diff --git a/src/BattleParamsIndex.cc b/src/BattleParamsIndex.cc index 385a8b53..e3c555f2 100644 --- a/src/BattleParamsIndex.cc +++ b/src/BattleParamsIndex.cc @@ -33,12 +33,12 @@ void BattleParamsIndex::Table::print(FILE* stream, Episode episode) const { names_str); }; - for (size_t diff = 0; diff < 4; diff++) { + for (Difficulty difficulty : ALL_DIFFICULTIES_V234) { phosg::fwrite_fmt(stream, "{} ZZ ATP PSV EVP HP DFP ATA LCK ESP EXP DIFF NAMES\n", - abbreviation_for_difficulty(diff)); + abbreviation_for_difficulty(difficulty)); for (size_t z = 0; z < 0x60; z++) { phosg::fwrite_fmt(stream, " {:02X} ", z); - print_entry(this->stats[diff][z], z); + print_entry(this->stats[static_cast(difficulty)][z], z); fputc('\n', stream); } } diff --git a/src/BattleParamsIndex.hh b/src/BattleParamsIndex.hh index 5db027f6..82c210ce 100644 --- a/src/BattleParamsIndex.hh +++ b/src/BattleParamsIndex.hh @@ -70,12 +70,25 @@ public: } __packed_ws__(MovementData, 0x30); struct Table { - /* 0000 */ parray, 4> stats; - /* 3600 */ parray, 4> attack_data; - /* 7E00 */ parray, 4> resist_data; - /* AE00 */ parray, 4> movement_data; + /* 0000 */ parray, 4> stats; // [difficulty][bp_index] + /* 3600 */ parray, 4> attack_data; // [difficulty][bp_index] + /* 7E00 */ parray, 4> resist_data; // [difficulty][bp_index] + /* AE00 */ parray, 4> movement_data; // [difficulty][bp_index] /* F600 */ + const PlayerStats& stats_for_index(Difficulty difficulty, uint8_t index) const { + return this->stats.at(static_cast(difficulty)).at(index); + } + const AttackData& attack_data_for_index(Difficulty difficulty, uint8_t index) const { + return this->attack_data.at(static_cast(difficulty)).at(index); + } + const ResistData& resist_data_for_index(Difficulty difficulty, uint8_t index) const { + return this->resist_data.at(static_cast(difficulty)).at(index); + } + const MovementData& movement_data_for_index(Difficulty difficulty, uint8_t index) const { + return this->movement_data.at(static_cast(difficulty)).at(index); + } + void print(FILE* stream, Episode episode) const; } __packed_ws__(Table, 0xF600); diff --git a/src/Channel.cc b/src/Channel.cc index ee414193..cf1df2d6 100644 --- a/src/Channel.cc +++ b/src/Channel.cc @@ -8,6 +8,7 @@ #include #include "Loggers.hh" +#include "StaticGameData.hh" #include "Version.hh" using namespace std; @@ -16,7 +17,7 @@ extern bool use_terminal_colors; Channel::Channel( Version version, - uint8_t language, + Language language, const string& name, phosg::TerminalFormat terminal_send_color, phosg::TerminalFormat terminal_recv_color) @@ -249,7 +250,7 @@ shared_ptr SocketChannel::create( std::shared_ptr io_context, std::unique_ptr&& sock, Version version, - uint8_t language, + Language language, const string& name, phosg::TerminalFormat terminal_send_color, phosg::TerminalFormat terminal_recv_color) { @@ -263,7 +264,7 @@ SocketChannel::SocketChannel( std::shared_ptr io_context, std::unique_ptr&& sock, Version version, - uint8_t language, + Language language, const string& name, phosg::TerminalFormat terminal_send_color, phosg::TerminalFormat terminal_recv_color) @@ -331,7 +332,7 @@ asio::awaitable SocketChannel::send_task() { PeerChannel::PeerChannel( std::shared_ptr io_context, Version version, - uint8_t language, + Language language, const std::string& name, phosg::TerminalFormat terminal_send_color, phosg::TerminalFormat terminal_recv_color) diff --git a/src/Channel.hh b/src/Channel.hh index 6dac0a92..f7fc4233 100644 --- a/src/Channel.hh +++ b/src/Channel.hh @@ -12,7 +12,7 @@ class Channel { public: Version version; - uint8_t language; + Language language; std::shared_ptr crypt_in; std::shared_ptr crypt_out; @@ -87,7 +87,7 @@ public: protected: Channel( Version version, - uint8_t language, + Language language, const std::string& name, phosg::TerminalFormat terminal_send_color = phosg::TerminalFormat::END, phosg::TerminalFormat terminal_recv_color = phosg::TerminalFormat::END); @@ -120,7 +120,7 @@ public: static std::shared_ptr create(std::shared_ptr io_context, std::unique_ptr&& sock, Version version, - uint8_t language, + Language language, const std::string& name = "", phosg::TerminalFormat terminal_send_color = phosg::TerminalFormat::END, phosg::TerminalFormat terminal_recv_color = phosg::TerminalFormat::END); @@ -138,7 +138,7 @@ private: std::shared_ptr io_context, std::unique_ptr&& sock, Version version, - uint8_t language, + Language language, const std::string& name, phosg::TerminalFormat terminal_send_color, phosg::TerminalFormat terminal_recv_color); @@ -158,7 +158,7 @@ public: PeerChannel( std::shared_ptr io_context, Version version, - uint8_t language, + Language language, const std::string& name = "", phosg::TerminalFormat terminal_send_color = phosg::TerminalFormat::END, phosg::TerminalFormat terminal_recv_color = phosg::TerminalFormat::END); diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index 8e5c3405..2ca8e4cf 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -966,7 +966,7 @@ ChatCommandDefinition cc_edit( if (tokens.at(1).size() != 1) { throw runtime_error("invalid language"); } - uint8_t new_language = language_code_for_char(tokens.at(1).at(0)); + Language new_language = language_for_char(tokens.at(1).at(0)); a.c->channel->language = new_language; p->inventory.language = new_language; p->guild_card.language = new_language; @@ -1829,7 +1829,7 @@ ChatCommandDefinition cc_playrec( auto record = make_shared(data); auto battle_player = make_shared(s->io_context, record); auto game = create_game_generic( - s, a.c, a.text, "", Episode::EP3, GameMode::NORMAL, 0, false, nullptr, battle_player); + s, a.c, a.text, "", Episode::EP3, GameMode::NORMAL, Difficulty::NORMAL, false, nullptr, battle_player); if (game) { if (start_battle_player_immediately) { game->set_flag(Lobby::Flag::START_BATTLE_PLAYER_IMMEDIATELY); @@ -1920,8 +1920,8 @@ static void command_qset_qclear(const Args& a, bool should_set) { a.c->proxy_session->server_channel->send(0x60, 0x00, &cmd, sizeof(cmd)); } } else { - uint8_t difficulty = a.c->proxy_session ? a.c->proxy_session->lobby_difficulty : a.c->require_lobby()->difficulty; - G_UpdateQuestFlag_V3_BB_6x75 cmd = {{{0x75, 0x03, 0x0000}, flag_num, should_set ? 0 : 1}, difficulty, 0x0000}; + Difficulty difficulty = a.c->proxy_session ? a.c->proxy_session->lobby_difficulty : a.c->require_lobby()->difficulty; + G_UpdateQuestFlag_V3_BB_6x75 cmd = {{{0x75, 0x03, 0x0000}, flag_num, should_set ? 0 : 1}, static_cast(difficulty), 0x0000}; a.c->channel->send(0x60, 0x00, &cmd, sizeof(cmd)); if (a.c->proxy_session) { a.c->proxy_session->server_channel->send(0x60, 0x00, &cmd, sizeof(cmd)); @@ -2892,7 +2892,7 @@ static void whatobj_whatene_fn(const Args& a, bool include_objs, bool include_en const auto* set_entry = nearest_ene->super_ene->version(a.c->version()).set_entry; string type_name = MapFile::name_for_enemy_type(set_entry->base_type, a.c->version(), area); send_text_message_fmt(a.c, "$C5E-{:03X}\n$C6{}\n$C2{}\n$C7X:{:.2f} Z:{:.2f}", - nearest_ene->e_id, phosg::name_for_enum(nearest_ene->type(a.c->version(), l->episode, l->event)), + nearest_ene->e_id, phosg::name_for_enum(nearest_ene->type(a.c->version(), l->episode, l->difficulty, l->event)), type_name, nearest_worldspace_pos.x, nearest_worldspace_pos.z); auto set_str = set_entry->str(a.c->version(), area); a.c->log.info_f("Enemy found via $whatobj: E-{:03X} {} at x={:g} y={:g} z={:g}", diff --git a/src/Client.cc b/src/Client.cc index fcd146d9..4d28626d 100644 --- a/src/Client.cc +++ b/src/Client.cc @@ -371,7 +371,7 @@ bool Client::evaluate_quest_availability_expression( shared_ptr expr, shared_ptr game, uint8_t event, - uint8_t difficulty, + Difficulty difficulty, size_t num_players, bool v1_present) const { if (this->login && this->login->account->check_flag(Account::Flag::DISABLE_QUEST_REQUIREMENTS)) { @@ -385,7 +385,7 @@ bool Client::evaluate_quest_availability_expression( } auto p = this->character_file(); IntegralExpression::Env env = { - .flags = &p->quest_flags.data.at(difficulty), + .flags = &p->quest_flags.data.at(static_cast(difficulty)), .challenge_records = &p->challenge_records, .team = this->team(), .num_players = num_players, @@ -404,7 +404,7 @@ bool Client::can_see_quest( shared_ptr q, shared_ptr game, uint8_t event, - uint8_t difficulty, + Difficulty difficulty, size_t num_players, bool v1_present) const { if (!q->has_version_any_language(this->version())) { @@ -418,7 +418,7 @@ bool Client::can_play_quest( shared_ptr q, shared_ptr game, uint8_t event, - uint8_t difficulty, + Difficulty difficulty, size_t num_players, bool v1_present) const { if (!q->has_version_any_language(this->version())) { @@ -618,7 +618,7 @@ void Client::save_character_file() { void Client::create_character_file( uint32_t guild_card_number, - uint8_t language, + Language language, const PlayerDispDataBBPreview& preview, shared_ptr level_table) { this->character_data = PSOBBCharacterFile::create_from_preview(guild_card_number, language, preview, level_table); @@ -1027,8 +1027,8 @@ void Client::load_all_files() { void Client::update_character_data_after_load(shared_ptr charfile) { charfile->import_tethealla_material_usage(this->require_server_state()->level_table(this->version())); - uint8_t lang = this->language(); - this->log.info_f("Overriding language fields in save files with {:02X} ({})", lang, char_for_language_code(lang)); + Language lang = this->language(); + this->log.info_f("Overriding language fields in save files with {}", name_for_language(lang)); charfile->inventory.language = lang; charfile->guild_card.language = lang; } diff --git a/src/Client.hh b/src/Client.hh index eea43573..c1f29297 100644 --- a/src/Client.hh +++ b/src/Client.hh @@ -228,7 +228,7 @@ public: inline Version version() const { return this->channel->version; } - inline uint8_t language() const { + inline Language language() const { return this->channel->language; } @@ -267,21 +267,21 @@ public: std::shared_ptr expr, std::shared_ptr game, uint8_t event, - uint8_t difficulty, + Difficulty difficulty, size_t num_players, bool v1_present) const; bool can_see_quest( std::shared_ptr q, std::shared_ptr game, uint8_t event, - uint8_t difficulty, + Difficulty difficulty, size_t num_players, bool v1_present) const; bool can_play_quest( std::shared_ptr q, std::shared_ptr game, uint8_t event, - uint8_t difficulty, + Difficulty difficulty, size_t num_players, bool v1_present) const; @@ -316,7 +316,7 @@ public: void save_character_file(); void create_character_file( uint32_t guild_card_number, - uint8_t language, + Language language, const PlayerDispDataBBPreview& preview, std::shared_ptr level_table); void create_battle_overlay(std::shared_ptr rules, std::shared_ptr level_table); diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index e749be6b..a586dae6 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -349,7 +349,7 @@ struct C_LegacyLogin_PC_V3_03 { /* 00 */ be_uint64_t hardware_id; /* 08 */ le_uint32_t sub_version = 0; /* 0C */ uint8_t is_extended = 0; - /* 0D */ uint8_t language = 0; + /* 0D */ Language language = Language::JAPANESE; /* 0E */ le_uint16_t unused = 0; // Note: These are suffixed with 2 since they come from the same source data // as the corresponding fields in 9D/9E. (Even though serial_number and @@ -404,7 +404,7 @@ struct C_LegacyLogin_PC_V3_04 { /* 00 */ be_uint64_t hardware_id; /* 08 */ le_uint32_t sub_version = 0; /* 0C */ uint8_t is_extended = 0; - /* 0D */ uint8_t language = 0; + /* 0D */ Language language = Language::JAPANESE; /* 0E */ le_uint16_t unused = 0; /* 10 */ pstring serial_number; /* 20 */ pstring access_key; @@ -414,7 +414,7 @@ struct C_LegacyLogin_PC_V3_04 { struct C_LegacyLogin_BB_04 { /* 00 */ le_uint32_t sub_version = 0; /* 04 */ uint8_t is_extended = 0; - /* 05 */ uint8_t language = 0; + /* 05 */ Language language = Language::JAPANESE; /* 06 */ le_uint16_t unused = 0; /* 08 */ pstring username; /* 18 */ pstring password; @@ -1286,7 +1286,7 @@ struct S_JoinGameT_DC_PC { /* 0104 */ uint8_t client_id = 0; /* 0105 */ uint8_t leader_id = 0; /* 0106 */ uint8_t disable_udp = 1; - /* 0107 */ uint8_t difficulty = 0; + /* 0107 */ Difficulty difficulty = Difficulty::NORMAL; /* 0108 */ uint8_t battle_mode = 0; /* 0109 */ uint8_t event = 0; /* 010A */ uint8_t section_id = 0; @@ -1687,7 +1687,7 @@ struct C_Login_DCNTE_8B { be_uint64_t hardware_id; le_uint32_t sub_version = 0x20; uint8_t is_extended = 0; - uint8_t language = 0; + Language language = Language::JAPANESE; parray unused1; pstring serial_number; pstring access_key; @@ -1748,7 +1748,7 @@ struct C_RegisterV1_DC_92 { be_uint64_t hardware_id; le_uint32_t sub_version; uint8_t unused1 = 0; - uint8_t language = 0; + Language language = Language::JAPANESE; parray unused2; pstring serial_number2; pstring access_key2; @@ -1767,7 +1767,7 @@ struct C_LoginV1_DC_93 { /* 08 */ be_uint64_t hardware_id; /* 10 */ le_uint32_t sub_version = 0; /* 14 */ uint8_t is_extended = 0; - /* 15 */ uint8_t language = 0; + /* 15 */ Language language = Language::JAPANESE; /* 16 */ parray unused1; /* 18 */ pstring serial_number; /* 29 */ pstring access_key; @@ -1788,7 +1788,7 @@ struct C_LoginBase_BB_93 { /* 00 */ le_uint32_t player_tag = 0x00010000; /* 04 */ le_uint32_t guild_card_number = 0; /* 08 */ le_uint32_t sub_version = 0; - /* 0C */ uint8_t language = 0; + /* 0C */ Language language = Language::JAPANESE; /* 0D */ int8_t character_slot = 0; // Values for connection_phase: // 00 - initial connection (client will request system file, characters, etc.) @@ -1952,7 +1952,7 @@ struct C_Register_DC_PC_V3_9C { /* 00 */ be_uint64_t hardware_id; /* 08 */ le_uint32_t sub_version = 0; /* 0C */ uint8_t unused1 = 0; - /* 0D */ uint8_t language = 0; + /* 0D */ Language language = Language::JAPANESE; /* 0E */ parray unused2; /* 10 */ pstring serial_number; // On XB, this is the XBL gamertag /* 40 */ pstring access_key; // On XB, this is the XBL user ID @@ -1963,7 +1963,7 @@ struct C_Register_DC_PC_V3_9C { struct C_Register_BB_9C { le_uint32_t sub_version = 0; uint8_t unused1 = 0; - uint8_t language = 0; + Language language = Language::JAPANESE; parray unused2; pstring username; pstring password; @@ -2002,7 +2002,7 @@ struct C_Login_DC_PC_GC_9D { /* 08 */ be_uint64_t hardware_id; /* 10 */ le_uint32_t sub_version = 0; /* 14 */ uint8_t is_extended = 0; // If 1, structure has extended format - /* 15 */ uint8_t language = 0; // 0 = JP, 1 = EN, 2 = DE, 3 = FR, 4 = ES + /* 15 */ Language language = Language::JAPANESE; // 0 = JP, 1 = EN, 2 = DE, 3 = FR, 4 = ES /* 16 */ parray unused3; // Always zeroes /* 18 */ pstring v1_serial_number; /* 28 */ pstring v1_access_key; @@ -2062,7 +2062,7 @@ struct C_LoginExtended_BB_9E { /* 0000 */ le_uint32_t player_tag = 0x00010000; /* 0004 */ le_uint32_t guild_card_number = 0; // == account_id when on newserv /* 0008 */ le_uint32_t sub_version = 0; - /* 000C */ le_uint32_t language = 0; + /* 000C */ le_uint32_t language32 = 0; /* 0010 */ le_uint32_t unknown_a2 = 0; /* 0014 */ pstring v1_serial_number; // Always blank? /* 0024 */ pstring v1_access_key; // == "?" @@ -2570,7 +2570,7 @@ check_struct_size(C_CreateGame_DCNTE, 0x28); template struct C_CreateGameT : C_CreateGameBaseT { - uint8_t difficulty = 0; // 0-3 (always 0 on Episode 3) + Difficulty difficulty = Difficulty::NORMAL; // Always NORMAL on Episode 3 uint8_t battle_mode = 0; // 0 or 1 (always 0 on Episode 3) // Note: Episode 3 uses the challenge mode flag for view battle permissions. // 0 = view battle allowed; 1 = not allowed @@ -2915,7 +2915,7 @@ struct C_SetChallengeModeCharacterTemplate_BB_02DF { struct C_SetChallengeModeDifficulty_BB_03DF { // No existing challenge mode quest sets this to a value other than zero. - le_uint32_t difficulty = 0; + le_uint32_t difficulty32 = 0; } __packed_ws__(C_SetChallengeModeDifficulty_BB_03DF, 4); struct C_SetChallengeModeEXPMultiplier_BB_04DF { @@ -3324,7 +3324,7 @@ struct S_JoinSpectatorTeam_Ep3_E8 { /* 1170 */ uint8_t client_id = 0; /* 1171 */ uint8_t leader_id = 0; /* 1172 */ uint8_t disable_udp = 1; - /* 1173 */ uint8_t difficulty = 0; + /* 1173 */ Difficulty difficulty = Difficulty::NORMAL; /* 1174 */ uint8_t battle_mode = 0; /* 1175 */ uint8_t event = 0; /* 1176 */ uint8_t section_id = 0; @@ -5145,7 +5145,7 @@ struct G_6x70_Base_V1 { /* 0040 */ StatusEffectState attack_status_effect; /* 004C */ StatusEffectState defense_status_effect; /* 0058 */ StatusEffectState unused_status_effect; - /* 0064 */ le_uint32_t language = 0; + /* 0064 */ le_uint32_t language32 = 0; /* 0068 */ le_uint32_t player_tag = 0; /* 006C */ le_uint32_t guild_card_number = 0; /* 0070 */ le_uint32_t unknown_a6 = 0; // Probably battle-related (assigned together with battle_team_number) @@ -5256,7 +5256,7 @@ struct G_UpdateQuestFlag_DC_PC_6x75 { } __packed_ws__(G_UpdateQuestFlag_DC_PC_6x75, 8); struct G_UpdateQuestFlag_V3_BB_6x75 : G_UpdateQuestFlag_DC_PC_6x75 { - le_uint16_t difficulty = 0; + le_uint16_t difficulty16 = 0; le_uint16_t unused = 0; } __packed_ws__(G_UpdateQuestFlag_V3_BB_6x75, 0x0C); diff --git a/src/CommonItemSet.cc b/src/CommonItemSet.cc index 7480b9e8..00551f67 100644 --- a/src/CommonItemSet.cc +++ b/src/CommonItemSet.cc @@ -122,8 +122,7 @@ CommonItemSet::Table::Table(const phosg::JSON& json, Episode episode) const auto& enemy_type_drop_probs_json = json.at("EnemyTypeDropProbs").as_dict(); const auto& enemy_item_classes_json = json.at("EnemyItemClasses").as_dict(); for (size_t z = 0; z < 0x64; z++) { - static const array episodes = {Episode::EP1, Episode::EP2, Episode::EP4}; - for (Episode episode : episodes) { + for (Episode episode : ALL_EPISODES_V4) { auto types = enemy_types_for_rare_table_index(episode, z); vector names; if (types.empty()) { @@ -460,8 +459,7 @@ phosg::JSON CommonItemSet::Table::json() const { phosg::JSON enemy_type_drop_probs_json = phosg::JSON::dict(); phosg::JSON enemy_item_classes_json = phosg::JSON::dict(); for (size_t z = 0; z < 0x64; z++) { - static const array episodes = {Episode::EP1, Episode::EP2, Episode::EP4}; - for (Episode episode : episodes) { + for (Episode episode : ALL_EPISODES_V4) { auto types = enemy_types_for_rare_table_index(episode, z); vector names; if (types.empty()) { @@ -506,13 +504,11 @@ phosg::JSON CommonItemSet::Table::json() const { phosg::JSON CommonItemSet::json() const { auto modes_dict = phosg::JSON::dict(); - static const array modes = {GameMode::NORMAL, GameMode::BATTLE, GameMode::CHALLENGE, GameMode::SOLO}; - for (const auto& mode : modes) { + for (const auto& mode : ALL_GAME_MODES_V4) { auto episodes_dict = phosg::JSON::dict(); - static const array episodes = {Episode::EP1, Episode::EP2, Episode::EP4}; - for (const auto& episode : episodes) { + for (const auto& episode : ALL_EPISODES_V4) { auto difficulty_dict = phosg::JSON::dict(); - for (uint8_t difficulty = 0; difficulty < 4; difficulty++) { + for (const auto& difficulty : ALL_DIFFICULTIES_V234) { auto section_id_dict = phosg::JSON::dict(); for (uint8_t section_id = 0; section_id < 10; section_id++) { try { @@ -532,11 +528,9 @@ phosg::JSON CommonItemSet::json() const { } void CommonItemSet::print(FILE* stream) const { - static const array modes = {GameMode::NORMAL, GameMode::BATTLE, GameMode::CHALLENGE, GameMode::SOLO}; - for (const auto& mode : modes) { - static const array episodes = {Episode::EP1, Episode::EP2, Episode::EP4}; - for (const auto& episode : episodes) { - for (uint8_t difficulty = 0; difficulty < 4; difficulty++) { + for (const auto& mode : ALL_GAME_MODES_V4) { + for (const auto& episode : ALL_EPISODES_V4) { + for (Difficulty difficulty : ALL_DIFFICULTIES_V234) { for (uint8_t section_id = 0; section_id < 10; section_id++) { try { auto table = this->get_table(episode, mode, difficulty, section_id); @@ -552,11 +546,9 @@ void CommonItemSet::print(FILE* stream) const { } void CommonItemSet::print_diff(FILE* stream, const CommonItemSet& other) const { - static const array modes = {GameMode::NORMAL, GameMode::BATTLE, GameMode::CHALLENGE, GameMode::SOLO}; - for (const auto& mode : modes) { - static const array episodes = {Episode::EP1, Episode::EP2, Episode::EP4}; - for (const auto& episode : episodes) { - for (uint8_t difficulty = 0; difficulty < 4; difficulty++) { + for (const auto& mode : ALL_GAME_MODES_V4) { + for (const auto& episode : ALL_EPISODES_V4) { + for (const auto& difficulty : ALL_DIFFICULTIES_V234) { for (uint8_t section_id = 0; section_id < 10; section_id++) { shared_ptr this_table; shared_ptr other_table; @@ -654,7 +646,7 @@ void CommonItemSet::Table::parse_itempt_t(const phosg::StringReader& r, bool is_ this->box_item_class_prob_table = r.pget, 7>>(offsets.box_item_class_prob_table_offset); } -uint16_t CommonItemSet::key_for_table(Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) { +uint16_t CommonItemSet::key_for_table(Episode episode, GameMode mode, Difficulty difficulty, uint8_t secid) { // Bits: -----EEEMMDDSSSS return (((static_cast(episode) << 8) & 0x0700) | ((static_cast(mode) << 6) & 0x00C0) | @@ -663,12 +655,12 @@ uint16_t CommonItemSet::key_for_table(Episode episode, GameMode mode, uint8_t di } shared_ptr CommonItemSet::get_table( - Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) const { + Episode episode, GameMode mode, Difficulty difficulty, uint8_t secid) const { try { return this->tables.at(this->key_for_table(episode, mode, difficulty, secid)); } catch (const out_of_range&) { throw runtime_error(std::format("common item table not available for episode={}, mode={}, difficulty={}, secid={}", - name_for_episode(episode), name_for_mode(mode), difficulty, secid)); + name_for_episode(episode), name_for_mode(mode), name_for_difficulty(difficulty), secid)); } } @@ -678,17 +670,20 @@ AFSV2CommonItemSet::AFSV2CommonItemSet( // Hard, etc. { AFSArchive pt_afs(pt_afs_data); - size_t max_difficulty; + bool include_ultimate; if (pt_afs.num_entries() >= 40) { - max_difficulty = 4; + include_ultimate = true; } else if (pt_afs.num_entries() >= 30) { - max_difficulty = 3; + include_ultimate = false; } else { throw std::runtime_error(std::format("PT AFS file has unexpected entry count ({})", pt_afs.num_entries())); } - for (size_t difficulty = 0; difficulty < max_difficulty; difficulty++) { + for (Difficulty difficulty : ALL_DIFFICULTIES_V234) { + if ((difficulty == Difficulty::ULTIMATE) && !include_ultimate) { + continue; + } for (size_t section_id = 0; section_id < 10; section_id++) { - auto entry = pt_afs.get(difficulty * 10 + section_id); + auto entry = pt_afs.get(static_cast(difficulty) * 10 + section_id); phosg::StringReader r(entry.first, entry.second); auto table = make_shared(r, false, false, Episode::EP1); this->tables.emplace(this->key_for_table(Episode::EP1, GameMode::NORMAL, difficulty, section_id), table); @@ -702,16 +697,19 @@ AFSV2CommonItemSet::AFSV2CommonItemSet( // 30th are used (section_id is ignored) if (ct_afs_data) { AFSArchive ct_afs(ct_afs_data); - size_t max_difficulty; + bool include_ultimate; if (ct_afs.num_entries() >= 40) { - max_difficulty = 4; + include_ultimate = true; } else if (ct_afs.num_entries() >= 30) { - max_difficulty = 3; + include_ultimate = false; } else { throw std::runtime_error(std::format("CT AFS file has unexpected entry count ({})", ct_afs.num_entries())); } - for (size_t difficulty = 0; difficulty < max_difficulty; difficulty++) { - auto r = ct_afs.get_reader(difficulty * 10); + for (Difficulty difficulty : ALL_DIFFICULTIES_V234) { + if ((difficulty == Difficulty::ULTIMATE) && !include_ultimate) { + continue; + } + auto r = ct_afs.get_reader(static_cast(difficulty) * 10); auto table = make_shared
(r, false, false, Episode::EP1); for (size_t section_id = 0; section_id < 10; section_id++) { this->tables.emplace(this->key_for_table(Episode::EP1, GameMode::CHALLENGE, difficulty, section_id), table); @@ -723,7 +721,7 @@ AFSV2CommonItemSet::AFSV2CommonItemSet( GSLV3V4CommonItemSet::GSLV3V4CommonItemSet(std::shared_ptr gsl_data, bool is_big_endian) { GSLArchive gsl(gsl_data, is_big_endian); - auto filename_for_table = +[](Episode episode, uint8_t difficulty, uint8_t section_id, bool is_challenge) -> string { + auto filename_for_table = +[](Episode episode, Difficulty difficulty, uint8_t section_id, bool is_challenge) -> string { const char* episode_token = ""; switch (episode) { case Episode::EP1: @@ -746,9 +744,8 @@ GSLV3V4CommonItemSet::GSLV3V4CommonItemSet(std::shared_ptr gs section_id); }; - vector episodes = {Episode::EP1, Episode::EP2, Episode::EP4}; - for (Episode episode : episodes) { - for (size_t difficulty = 0; difficulty < 4; difficulty++) { + for (Episode episode : ALL_EPISODES_V4) { + for (Difficulty difficulty : ALL_DIFFICULTIES_V234) { for (size_t section_id = 0; section_id < 10; section_id++) { phosg::StringReader r; try { @@ -773,7 +770,7 @@ GSLV3V4CommonItemSet::GSLV3V4CommonItemSet(std::shared_ptr gs } if (episode != Episode::EP4) { - for (size_t difficulty = 0; difficulty < 4; difficulty++) { + for (Difficulty difficulty : ALL_DIFFICULTIES_V234) { try { auto r = gsl.get_reader(filename_for_table(episode, difficulty, 0, true)); auto table = make_shared
(r, is_big_endian, true, episode); @@ -800,9 +797,9 @@ JSONCommonItemSet::JSONCommonItemSet(const phosg::JSON& json) { Episode episode = episode_keys.at(episode_it.first); for (const auto& difficulty_it : episode_it.second->as_dict()) { - static const unordered_map difficulty_keys( - {{"Normal", 0}, {"Hard", 1}, {"VeryHard", 2}, {"Ultimate", 3}}); - uint8_t difficulty = difficulty_keys.at(difficulty_it.first); + static const unordered_map difficulty_keys( + {{"Normal", Difficulty::NORMAL}, {"Hard", Difficulty::HARD}, {"VeryHard", Difficulty::VERY_HARD}, {"Ultimate", Difficulty::ULTIMATE}}); + Difficulty difficulty = difficulty_keys.at(difficulty_it.first); for (const auto& section_id_it : difficulty_it.second->as_dict()) { uint8_t section_id = section_id_for_name(section_id_it.first); diff --git a/src/CommonItemSet.hh b/src/CommonItemSet.hh index 16f2c6ee..10980d76 100644 --- a/src/CommonItemSet.hh +++ b/src/CommonItemSet.hh @@ -271,7 +271,7 @@ public: bool operator==(const CommonItemSet& other) const = default; bool operator!=(const CommonItemSet& other) const = default; - std::shared_ptr get_table(Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid) const; + std::shared_ptr get_table(Episode episode, GameMode mode, Difficulty difficulty, uint8_t secid) const; phosg::JSON json() const; void print(FILE* stream) const; void print_diff(FILE* stream, const CommonItemSet& other) const; @@ -279,7 +279,7 @@ public: protected: CommonItemSet() = default; - static uint16_t key_for_table(Episode episode, GameMode mode, uint8_t difficulty, uint8_t secid); + static uint16_t key_for_table(Episode episode, GameMode mode, Difficulty difficulty, uint8_t secid); std::unordered_map> tables; }; diff --git a/src/DownloadSession.cc b/src/DownloadSession.cc index bf1a3a38..41d2c914 100644 --- a/src/DownloadSession.cc +++ b/src/DownloadSession.cc @@ -40,7 +40,7 @@ DownloadSession::DownloadSession( uint16_t remote_port, const std::string& output_dir, Version version, - uint8_t language, + Language language, std::shared_ptr bb_key_file, uint32_t serial_number2, uint32_t serial_number, @@ -222,13 +222,13 @@ void DownloadSession::send_61_98(bool is_98) { if (is_v1(this->version)) { C_CharacterData_DCv1_61_98 ret; ret.inventory = this->character->inventory; - ret.disp = convert_player_disp_data(this->character->disp, 1, 1); + ret.disp = convert_player_disp_data(this->character->disp, Language::ENGLISH, Language::ENGLISH); this->channel->send(command, 0x01, ret); } else if (this->version == Version::DC_V2) { C_CharacterData_DCv2_61_98 ret; ret.inventory = this->character->inventory; - ret.disp = convert_player_disp_data(this->character->disp, 1, 1); + ret.disp = convert_player_disp_data(this->character->disp, Language::ENGLISH, Language::ENGLISH); ret.records.challenge = this->character->challenge_records; ret.records.battle = this->character->battle_records; ret.choice_search_config = this->character->choice_search_config; @@ -237,7 +237,7 @@ void DownloadSession::send_61_98(bool is_98) { } else if (this->version == Version::PC_V2) { C_CharacterData_PC_61_98 ret; ret.inventory = this->character->inventory; - ret.disp = convert_player_disp_data(this->character->disp, 1, 1); + ret.disp = convert_player_disp_data(this->character->disp, Language::ENGLISH, Language::ENGLISH); ret.records.challenge = this->character->challenge_records; ret.records.battle = this->character->battle_records; ret.choice_search_config = this->character->choice_search_config; @@ -246,7 +246,7 @@ void DownloadSession::send_61_98(bool is_98) { } else if (is_v3(this->version)) { C_CharacterData_V3_61_98 ret; ret.inventory = this->character->inventory; - ret.disp = convert_player_disp_data(this->character->disp, 1, 1); + ret.disp = convert_player_disp_data(this->character->disp, Language::ENGLISH, Language::ENGLISH); ret.records.challenge = this->character->challenge_records; ret.records.battle = this->character->battle_records; ret.choice_search_config = this->character->choice_search_config; @@ -552,7 +552,7 @@ asio::awaitable DownloadSession::on_message(Channel::Message& msg) { C_CreateGame_PC_C1 ret; ret.name.encode(random_name()); ret.password.encode(random_name()); - ret.difficulty = 0; + ret.difficulty = Difficulty::NORMAL; ret.battle_mode = (game_config.mode == GameMode::BATTLE); ret.challenge_mode = (game_config.mode == GameMode::CHALLENGE); ret.episode = 1; @@ -562,7 +562,7 @@ asio::awaitable DownloadSession::on_message(Channel::Message& msg) { C_CreateGame_DC_V3_0C_C1_Ep3_EC ret; ret.name.encode(random_name()); ret.password.encode(random_name()); - ret.difficulty = 0; + ret.difficulty = Difficulty::NORMAL; ret.battle_mode = (game_config.mode == GameMode::BATTLE); ret.challenge_mode = (game_config.mode == GameMode::CHALLENGE); if (is_v1(this->version)) { @@ -582,7 +582,7 @@ asio::awaitable DownloadSession::on_message(Channel::Message& msg) { C_CreateGame_BB_C1 ret; ret.name.encode(random_name()); ret.password.encode(random_name()); - ret.difficulty = 0; + ret.difficulty = Difficulty::NORMAL; ret.battle_mode = (game_config.mode == GameMode::BATTLE); ret.challenge_mode = (game_config.mode == GameMode::CHALLENGE); if (game_config.episode == Episode::EP1) { @@ -651,12 +651,12 @@ asio::awaitable DownloadSession::on_message(Channel::Message& msg) { filtered_name.push_back((isalnum(ch) || (ch == '-') || (ch == '.') || (ch == '_')) ? ch : '_'); } string local_filename = std::format( - "{}/{:016X}_{}_{}_{}_{}", + "{}/{:016X}_{}_{}_{:c}_{}", this->output_dir, this->current_request, phosg::now(), phosg::name_for_enum(this->version), - char_for_language_code(this->language), + char_for_language(this->language), filtered_name); this->open_files.emplace(internal_name, OpenFile{.request = this->current_request, .filename = local_filename, .total_size = cmd.file_size, .data = ""}); }; diff --git a/src/DownloadSession.hh b/src/DownloadSession.hh index 298d5e5c..155736db 100644 --- a/src/DownloadSession.hh +++ b/src/DownloadSession.hh @@ -21,7 +21,7 @@ public: uint16_t remote_port, const std::string& output_dir, Version version, - uint8_t language, + Language language, std::shared_ptr bb_key_file, uint32_t serial_number2, uint32_t serial_number, @@ -50,7 +50,7 @@ protected: uint16_t remote_port; std::string output_dir; Version version; - uint8_t language; + Language language; bool show_command_data; std::shared_ptr bb_key_file; uint32_t serial_number; diff --git a/src/Episode3/DataIndexes.cc b/src/Episode3/DataIndexes.cc index 9fb63249..99a35ef0 100644 --- a/src/Episode3/DataIndexes.cc +++ b/src/Episode3/DataIndexes.cc @@ -1720,7 +1720,7 @@ phosg::JSON MapDefinition::CameraSpec::json() const { }); } -phosg::JSON MapDefinition::NPCDeck::json(uint8_t language) const { +phosg::JSON MapDefinition::NPCDeck::json(Language language) const { phosg::JSON card_ids_json = phosg::JSON::list(); for (size_t z = 0; z < this->card_ids.size(); z++) { if (this->card_ids[z] != 0xFFFF) { @@ -1733,7 +1733,7 @@ phosg::JSON MapDefinition::NPCDeck::json(uint8_t language) const { }); } -phosg::JSON MapDefinition::AIParams::json(uint8_t language) const { +phosg::JSON MapDefinition::AIParams::json(Language language) const { phosg::JSON params_json = phosg::JSON::list(); for (size_t z = 0; z < this->params.size(); z++) { params_json.emplace_back(this->params[z].load()); @@ -1745,7 +1745,7 @@ phosg::JSON MapDefinition::AIParams::json(uint8_t language) const { }); } -phosg::JSON MapDefinition::DialogueSet::json(uint8_t language) const { +phosg::JSON MapDefinition::DialogueSet::json(Language language) const { phosg::JSON strings_json = phosg::JSON::list(); for (size_t z = 0; z < this->strings.size(); z++) { strings_json.emplace_back(this->strings[z].decode(language)); @@ -1818,7 +1818,7 @@ string MapDefinition::CameraSpec::str() const { this->unknown_a2[1], this->unknown_a2[2]); } -string MapDefinition::str(const CardIndex* card_index, uint8_t language) const { +string MapDefinition::str(const CardIndex* card_index, Language language) const { deque lines; auto add_map = [&](const parray, 0x10>& tiles) { for (size_t y = 0; y < this->height; y++) { @@ -2503,7 +2503,7 @@ CardIndex::CardIndex( // Some cards intentionally have the same name, so we just leave them // unindexed (they can still be looked up by ID, of course) - string name = entry->def.en_name.decode(1); + string name = entry->def.en_name.decode(Language::ENGLISH); this->card_definitions_by_name.emplace(name, entry); this->card_definitions_by_name_normalized.emplace(this->normalize_card_name(name), entry); @@ -2620,11 +2620,11 @@ string CardIndex::normalize_card_name(const string& name) { return ret; } -MapIndex::VersionedMap::VersionedMap(shared_ptr map, uint8_t language) +MapIndex::VersionedMap::VersionedMap(shared_ptr map, Language language) : map(map), language(language) {} -MapIndex::VersionedMap::VersionedMap(std::string&& compressed_data, uint8_t language) +MapIndex::VersionedMap::VersionedMap(std::string&& compressed_data, Language language) : language(language), compressed_data(make_shared(std::move(compressed_data))) { string decompressed = prs_decompress(*this->compressed_data); @@ -2673,33 +2673,39 @@ std::shared_ptr MapIndex::VersionedMap::trial_download() cons MapIndex::Map::Map(shared_ptr initial_version) : map_number(initial_version->map->map_number), initial_version(initial_version) { - this->versions.resize(this->initial_version->language + 1); - this->versions[this->initial_version->language] = initial_version; + size_t lang_index = static_cast(this->initial_version->language); + this->versions.resize(lang_index + 1); + this->versions[lang_index] = initial_version; } void MapIndex::Map::add_version(std::shared_ptr vm) { - if (this->versions.size() <= vm->language) { - this->versions.resize(vm->language + 1); + size_t lang_index = static_cast(vm->language); + if (this->versions.size() <= lang_index) { + this->versions.resize(lang_index + 1); } - if (this->versions[vm->language]) { + if (this->versions[lang_index]) { throw runtime_error("map version already exists"); } this->initial_version->map->assert_semantically_equivalent(*vm->map); - this->versions[vm->language] = vm; + this->versions[lang_index] = vm; } -bool MapIndex::Map::has_version(uint8_t language) const { - return (this->versions.size() > language) && !!this->versions[language]; +bool MapIndex::Map::has_version(Language language) const { + size_t lang_index = static_cast(language); + return (this->versions.size() > lang_index) && !!this->versions[lang_index]; } -shared_ptr MapIndex::Map::version(uint8_t language) const { +shared_ptr MapIndex::Map::version(Language language) const { + size_t lang_index = static_cast(language); + // If the requested language exists, return it - if ((language < this->versions.size()) && this->versions[language]) { - return this->versions[language]; + if ((lang_index < this->versions.size()) && this->versions[lang_index]) { + return this->versions[lang_index]; } // If English exists, return it - if ((1 < this->versions.size()) && this->versions[1]) { - return this->versions[1]; + constexpr size_t english_lang_index = static_cast(Language::ENGLISH); + if ((english_lang_index < this->versions.size()) && this->versions[english_lang_index]) { + return this->versions[english_lang_index]; } // Return the first version that exists for (const auto& vm : this->versions) { @@ -2754,7 +2760,7 @@ MapIndex::MapIndex(const string& directory) { if (base_filename[base_filename.size() - 2] != '-') { throw runtime_error("language code not present"); } - uint8_t language = language_code_for_char(base_filename[base_filename.size() - 1]); + Language language = language_for_char(base_filename[base_filename.size() - 1]); shared_ptr vm; if (decompressed_data) { @@ -2773,7 +2779,7 @@ MapIndex::MapIndex(const string& directory) { static_game_data_log.debug_f("({}) Created Episode 3 map {:08X} {} ({}; {})", filename, vm->map->map_number, - char_for_language_code(vm->language), + char_for_language(vm->language), vm->map->is_quest() ? "quest" : "free", name); } else { @@ -2781,7 +2787,7 @@ MapIndex::MapIndex(const string& directory) { static_game_data_log.debug_f("({}) Added Episode 3 map version {:08X} {} ({}; {})", filename, vm->map->map_number, - char_for_language_code(vm->language), + char_for_language(vm->language), vm->map->is_quest() ? "quest" : "free", name); } @@ -2794,7 +2800,7 @@ MapIndex::MapIndex(const string& directory) { } } -const string& MapIndex::get_compressed_list(size_t num_players, uint8_t language) const { +const string& MapIndex::get_compressed_list(size_t num_players, Language language) const { if (num_players == 0) { throw runtime_error("cannot generate map list for no players"); } @@ -2802,10 +2808,11 @@ const string& MapIndex::get_compressed_list(size_t num_players, uint8_t language throw logic_error("player count is too high in map list generation"); } - if (language >= this->compressed_map_lists.size()) { - this->compressed_map_lists.resize(language + 1); + size_t lang_index = static_cast(language); + if (lang_index >= this->compressed_map_lists.size()) { + this->compressed_map_lists.resize(lang_index + 1); } - string& compressed_map_list = this->compressed_map_lists[language].at(num_players - 1); + string& compressed_map_list = this->compressed_map_lists[lang_index].at(num_players - 1); if (compressed_map_list.empty()) { phosg::StringWriter entries_w; phosg::StringWriter strings_w; diff --git a/src/Episode3/DataIndexes.hh b/src/Episode3/DataIndexes.hh index db6b8eec..cf8dbd0c 100644 --- a/src/Episode3/DataIndexes.hh +++ b/src/Episode3/DataIndexes.hh @@ -1315,7 +1315,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests /* 00 */ pstring deck_name; /* 18 */ parray card_ids; // Last one appears to always be FFFF /* 58 */ - phosg::JSON json(uint8_t language) const; + phosg::JSON json(Language language) const; } __packed_ws__(NPCDeck, 0x58); /* 1FE8 */ parray npc_decks; // Unused if name[0] == 0 @@ -1331,7 +1331,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests // TODO: Figure out exactly how these are used and document here. /* 0018 */ parray params; /* 0114 */ - phosg::JSON json(uint8_t language) const; + phosg::JSON json(Language language) const; } __packed_ws__(AIParams, 0x114); /* 20F0 */ parray npc_ai_params; // Unused if name[0] == 0 @@ -1384,7 +1384,7 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests // strings, excluding any that are empty or begin with the character '^'. /* 0004 */ parray, 4> strings; /* 0104 */ - phosg::JSON json(uint8_t language) const; + phosg::JSON json(Language language) const; } __packed_ws__(DialogueSet, 0x104); // There are up to 0x10 of these per valid NPC, but only the first 13 of them @@ -1490,8 +1490,8 @@ struct MapDefinition { // .mnmd format; also the format of (decompressed) quests // text may differ. void assert_semantically_equivalent(const MapDefinition& other) const; - std::string str(const CardIndex* card_index, uint8_t language) const; - phosg::JSON json(uint8_t language) const; + std::string str(const CardIndex* card_index, Language language) const; + phosg::JSON json(Language language) const; } __packed_ws__(MapDefinition, 0x5A18); struct MapDefinitionTrial { @@ -1592,10 +1592,10 @@ public: class VersionedMap { public: std::shared_ptr map; - uint8_t language; + Language language; - VersionedMap(std::shared_ptr map, uint8_t language); - VersionedMap(std::string&& compressed_data, uint8_t language); + VersionedMap(std::shared_ptr map, Language language); + VersionedMap(std::string&& compressed_data, Language language); std::shared_ptr trial() const; std::shared_ptr compressed(bool trial) const; @@ -1616,8 +1616,8 @@ public: explicit Map(std::shared_ptr initial_version); void add_version(std::shared_ptr vm); - bool has_version(uint8_t language) const; - std::shared_ptr version(uint8_t language) const; + bool has_version(Language language) const; + std::shared_ptr version(Language language) const; inline const std::vector>& all_versions() const { return this->versions; } @@ -1626,7 +1626,7 @@ public: std::vector> versions; }; - const std::string& get_compressed_list(size_t num_players, uint8_t language) const; + const std::string& get_compressed_list(size_t num_players, Language language) const; inline std::shared_ptr get(uint32_t id) const { return this->maps.at(id); } diff --git a/src/Episode3/DeckState.cc b/src/Episode3/DeckState.cc index 2fa565a4..a48443b9 100644 --- a/src/Episode3/DeckState.cc +++ b/src/Episode3/DeckState.cc @@ -320,7 +320,7 @@ void DeckState::print(FILE* stream, std::shared_ptr card_index) } } if (ce) { - string name = ce->def.en_name.decode(1); + string name = ce->def.en_name.decode(Language::ENGLISH); phosg::fwrite_fmt(stream, " ({:02}) index={:02X} ref=@{:04X} card_id=#{:04X} \"{}\" {}\n", z, e.deck_index, this->card_refs[z], e.card_id, name, name_for_card_state(e.state)); } else { diff --git a/src/Episode3/Server.cc b/src/Episode3/Server.cc index 4ac6079b..68012009 100644 --- a/src/Episode3/Server.cc +++ b/src/Episode3/Server.cc @@ -274,14 +274,14 @@ void Server::send_6xB4x46() const { // NTE doesn't have the date_str2 field, but we send it anyway to make // debugging easier. G_ServerVersionStrings_Ep3_6xB4x46 cmd; - cmd.version_signature.encode(this->options.is_nte() ? VERSION_SIGNATURE_NTE : VERSION_SIGNATURE, 1); - cmd.date_str1.encode(std::format("Card definitions: {:016X}", this->options.card_index->definitions_hash()), 1); + cmd.version_signature.encode(this->options.is_nte() ? VERSION_SIGNATURE_NTE : VERSION_SIGNATURE, Language::ENGLISH); + cmd.date_str1.encode(std::format("Card definitions: {:016X}", this->options.card_index->definitions_hash()), Language::ENGLISH); string build_date = phosg::format_time(BUILD_TIMESTAMP); - cmd.date_str2.encode(std::format("newserv {} compiled at {}", GIT_REVISION_HASH, build_date), 1); + cmd.date_str2.encode(std::format("newserv {} compiled at {}", GIT_REVISION_HASH, build_date), Language::ENGLISH); this->send(cmd); } -string Server::prepare_6xB6x41_map_definition(shared_ptr map, uint8_t language, bool is_nte) { +string Server::prepare_6xB6x41_map_definition(shared_ptr map, Language language, bool is_nte) { auto vm = map->version(language); const auto& compressed = vm->compressed(is_nte); @@ -306,7 +306,7 @@ void Server::send_commands_for_joining_spectator(std::shared_ptr ch) co if (this->last_chosen_map) { string data = this->prepare_6xB6x41_map_definition(this->last_chosen_map, ch->language, this->options.is_nte()); - this->log().info_f("Sending {} version of map {:08X}", char_for_language_code(ch->language), this->last_chosen_map->map_number); + this->log().info_f("Sending {} version of map {:08X}", name_for_language(ch->language), this->last_chosen_map->map_number); ch->send(0x6C, 0x00, data); } @@ -2137,7 +2137,7 @@ void Server::handle_CAx13_update_map_during_setup_t(shared_ptr c, const // in the case of NTE, no values at all, since the Rules structure is // smaller). So, use the values from the last chosen map if applicable, or // the values from the $dicerange command if available. - uint8_t language = c ? c->language() : 1; + Language language = c ? c->language() : Language::ENGLISH; const Rules* map_rules = this->last_chosen_map ? &this->last_chosen_map->version(language)->map->default_rules : nullptr; auto& server_rules = this->map_and_rules->rules; // NTE can specify the DEF dice value range in its Rules struct, so we use @@ -2526,7 +2526,7 @@ void Server::handle_CAx40_map_list_request(shared_ptr sender_c, const st } size_t num_players = l ? l->count_clients() : 1; - uint8_t language = sender_c ? sender_c->language() : 1; + Language language = sender_c ? sender_c->language() : Language::ENGLISH; const auto& list_data = this->options.map_index->get_compressed_list(num_players, language); phosg::StringWriter w; @@ -2553,15 +2553,16 @@ void Server::send_6xB6x41_to_all_clients() const { if (!c) { return; } - if (map_commands_by_language.size() <= c->language()) { - map_commands_by_language.resize(c->language() + 1); + size_t lang_index = static_cast(c->language()); + if (map_commands_by_language.size() <= lang_index) { + map_commands_by_language.resize(lang_index + 1); } - if (map_commands_by_language[c->language()].empty()) { - map_commands_by_language[c->language()] = this->prepare_6xB6x41_map_definition( + if (map_commands_by_language[lang_index].empty()) { + map_commands_by_language[lang_index] = this->prepare_6xB6x41_map_definition( this->last_chosen_map, c->language(), this->options.is_nte()); } - this->log().info_f("Sending {} version of map {:08X}", char_for_language_code(c->language()), this->last_chosen_map->map_number); - send_command(c, 0x6C, 0x00, map_commands_by_language[c->language()]); + this->log().info_f("Sending {} version of map {:08X}", name_for_language(c->language()), this->last_chosen_map->map_number); + send_command(c, 0x6C, 0x00, map_commands_by_language[lang_index]); }; for (const auto& c : l->clients) { send_to_client(c); @@ -2586,7 +2587,7 @@ void Server::send_6xB6x41_to_all_clients() const { } } else { - auto out_data = this->prepare_6xB6x41_map_definition(this->last_chosen_map, 1, false); + auto out_data = this->prepare_6xB6x41_map_definition(this->last_chosen_map, Language::ENGLISH, false); this->send(out_data.data(), out_data.size(), 0x6C, false); } } diff --git a/src/Episode3/Server.hh b/src/Episode3/Server.hh index 6f7929b6..83fe19a2 100644 --- a/src/Episode3/Server.hh +++ b/src/Episode3/Server.hh @@ -265,7 +265,7 @@ public: G_UpdateDecks_Ep3_6xB4x07 prepare_6xB4x07_decks_update() const; G_SetPlayerNames_Ep3_6xB4x1C prepare_6xB4x1C_names_update() const; - static std::string prepare_6xB6x41_map_definition(std::shared_ptr map, uint8_t language, bool is_nte); + static std::string prepare_6xB6x41_map_definition(std::shared_ptr map, Language language, bool is_nte); void send_6xB6x41_to_all_clients() const; G_SetTrapTileLocations_Ep3_6xB4x50 prepare_6xB4x50_trap_tile_locations() const; diff --git a/src/Episode3/Tournament.cc b/src/Episode3/Tournament.cc index cd619f83..c09f39df 100644 --- a/src/Episode3/Tournament.cc +++ b/src/Episode3/Tournament.cc @@ -737,7 +737,7 @@ string Tournament::bracket_str() const { } }; - auto en_vm = this->map->version(1); + auto en_vm = this->map->version(Language::ENGLISH); if (en_vm) { string map_name = en_vm->map->name.decode(en_vm->language); ret += std::format(" Map: {:08X} ({})\n", this->map->map_number, map_name); diff --git a/src/GameServer.cc b/src/GameServer.cc index 6f25c9b4..3e0aaa9f 100644 --- a/src/GameServer.cc +++ b/src/GameServer.cc @@ -128,7 +128,7 @@ shared_ptr GameServer::create_client(shared_ptr listen this->io_context, make_unique(std::move(client_sock)), listen_sock->version, - 1, + Language::ENGLISH, "", phosg::TerminalFormat::FG_YELLOW, phosg::TerminalFormat::FG_GREEN); diff --git a/src/HTTPServer.cc b/src/HTTPServer.cc index 44a96da2..6ad2a242 100644 --- a/src/HTTPServer.cc +++ b/src/HTTPServer.cc @@ -132,7 +132,7 @@ std::shared_ptr HTTPServer::generate_client_json( {"RemoteAddress", c->channel->default_name()}, {"Version", phosg::name_for_enum(c->version())}, {"SubVersion", c->sub_version}, - {"Language", name_for_language_code(c->language())}, + {"Language", name_for_language(c->language())}, {"LocationX", c->pos.x.load()}, {"LocationZ", c->pos.z.load()}, {"LocationFloor", c->floor}, @@ -173,7 +173,7 @@ std::shared_ptr HTTPServer::generate_client_json( if (p) { if (!is_ep3(c->version())) { if (c->version() != Version::DC_NTE) { - ret->emplace("InventoryLanguage", name_for_language_code(p->inventory.language)); + ret->emplace("InventoryLanguage", name_for_language(p->inventory.language)); ret->emplace("NumHPMaterialsUsed", p->get_material_usage(PSOBBCharacterFile::MaterialType::HP)); ret->emplace("NumTPMaterialsUsed", p->get_material_usage(PSOBBCharacterFile::MaterialType::TP)); if (!is_v1_or_v2(c->version())) { @@ -302,7 +302,7 @@ std::shared_ptr HTTPServer::generate_client_json( lobby_players_json.emplace_back(phosg::JSON::dict({ {"GuildCardNumber", p.guild_card_number}, {"Name", p.name}, - {"Language", name_for_language_code(p.language)}, + {"Language", name_for_language(p.language)}, {"SectionID", name_for_section_id(p.section_id)}, {"CharClass", name_for_char_class(p.char_class)}, })); @@ -481,7 +481,7 @@ std::shared_ptr HTTPServer::generate_lobby_json( } } deck_json = phosg::JSON::dict({ - {"Name", deck_entry->name.decode(lc ? lc->language() : 1)}, + {"Name", deck_entry->name.decode(lc ? lc->language() : Language::ENGLISH)}, {"TeamID", deck_entry->team_id.load()}, {"Cards", std::move(cards_json)}, {"GodWhimFlag", deck_entry->god_whim_flag}, @@ -490,7 +490,7 @@ std::shared_ptr HTTPServer::generate_lobby_json( } auto player_json = phosg::JSON::dict({ - {"PlayerName", ep3s->name_entries[z].name.decode(lc ? lc->language() : 1)}, + {"PlayerName", ep3s->name_entries[z].name.decode(lc ? lc->language() : Language::ENGLISH)}, {"ClientID", ep3s->name_entries[z].client_id}, {"IsCOM", !!ep3s->name_entries[z].is_cpu_player}, {"Deck", std::move(deck_json)}, @@ -608,7 +608,7 @@ std::shared_ptr HTTPServer::generate_summary_json() const { {"AccountID", c->login ? c->login->account->account_id : phosg::JSON(nullptr)}, {"Name", p ? p->disp.name.decode(c->language()) : phosg::JSON(nullptr)}, {"Version", phosg::name_for_enum(c->version())}, - {"Language", name_for_language_code(c->language())}, + {"Language", name_for_language(c->language())}, {"Level", p ? p->disp.stats.level + 1 : phosg::JSON(nullptr)}, {"Class", p ? name_for_char_class(p->disp.visual.char_class) : phosg::JSON(nullptr)}, {"SectionID", p ? name_for_section_id(p->disp.visual.section_id) : phosg::JSON(nullptr)}, diff --git a/src/IPStackSimulator.cc b/src/IPStackSimulator.cc index a0e894a9..b66a7d0b 100644 --- a/src/IPStackSimulator.cc +++ b/src/IPStackSimulator.cc @@ -166,7 +166,7 @@ IPSSChannel::IPSSChannel( std::weak_ptr ipss_client, std::weak_ptr tcp_conn, Version version, - uint8_t language, + Language language, const std::string& name, phosg::TerminalFormat terminal_send_color, phosg::TerminalFormat terminal_recv_color) @@ -1396,7 +1396,7 @@ asio::awaitable IPStackSimulator::open_server_connection( } const auto& port_config = port_config_it->second; - conn->server_channel = make_shared(this->shared_from_this(), c, conn, port_config->version, 1); + conn->server_channel = make_shared(this->shared_from_this(), c, conn, port_config->version, Language::ENGLISH); if (!this->state->game_server.get()) { this->log.error_f("No server available for TCP connection {}", conn_str); diff --git a/src/IPStackSimulator.hh b/src/IPStackSimulator.hh index 8854321a..2c4f4844 100644 --- a/src/IPStackSimulator.hh +++ b/src/IPStackSimulator.hh @@ -109,7 +109,7 @@ public: std::weak_ptr ipss_client, std::weak_ptr tcp_conn, Version version, - uint8_t language, + Language language, const std::string& name = "", phosg::TerminalFormat terminal_send_color = phosg::TerminalFormat::END, phosg::TerminalFormat terminal_recv_color = phosg::TerminalFormat::END); diff --git a/src/ImageEncoder.cc b/src/ImageEncoder.cc index fe21e9b8..020dbd6f 100644 --- a/src/ImageEncoder.cc +++ b/src/ImageEncoder.cc @@ -71,7 +71,7 @@ string encode_gvm(const phosg::ImageRGBA8888N& img, GVRDataFormat data_format, c w.put({.signature = 0x47564D48, .header_size = 0x48, .flags = 0x000F, .num_files = 1}); GVMFileEntry file_entry; file_entry.file_num = 0; - file_entry.name.encode(internal_name, 1); + file_entry.name.encode(internal_name, Language::ENGLISH); file_entry.data_format = data_format; file_entry.format_flags = 0; file_entry.dimensions = (dimensions_field << 4) | dimensions_field; diff --git a/src/ItemCreator.cc b/src/ItemCreator.cc index b56dba2c..a2378694 100644 --- a/src/ItemCreator.cc +++ b/src/ItemCreator.cc @@ -32,7 +32,7 @@ ItemCreator::ItemCreator( std::shared_ptr stack_limits, Episode episode, GameMode mode, - uint8_t difficulty, + Difficulty difficulty, uint8_t section_id, std::shared_ptr rand_crypt, shared_ptr restrictions) @@ -1072,7 +1072,7 @@ void ItemCreator::generate_armor_shop_armors(vector& shop, size_t play item.data1[1] = 1; item.data1[2] = pt.pop(); - if ((this->difficulty == 3) && (player_level > 99)) { + if ((this->difficulty == Difficulty::ULTIMATE) && (player_level > 99)) { if (player_level > 150) { item.data1[2] += 3; } else if (player_level >= 100) { @@ -1116,7 +1116,7 @@ void ItemCreator::generate_armor_shop_shields(vector& shop, size_t pla item.data1[1] = 2; item.data1[2] = pt.pop(); - if ((this->difficulty == 3) && (player_level > 99)) { + if ((this->difficulty == Difficulty::ULTIMATE) && (player_level > 99)) { if (player_level > 150) { item.data1[2] += 3; } else if (player_level >= 100) { @@ -1360,7 +1360,7 @@ vector ItemCreator::generate_weapon_shop_contents(size_t player_level) } size_t table_index; - if (this->difficulty == 3) { + if (this->difficulty == Difficulty::ULTIMATE) { if (player_level < 11) { table_index = 0; } else if (player_level < 26) { diff --git a/src/ItemCreator.hh b/src/ItemCreator.hh index b339eea6..b1a8bcac 100644 --- a/src/ItemCreator.hh +++ b/src/ItemCreator.hh @@ -22,7 +22,7 @@ public: std::shared_ptr stack_limits, Episode episode, GameMode mode, - uint8_t difficulty, + Difficulty difficulty, uint8_t section_id, std::shared_ptr rand_crypt, std::shared_ptr restrictions = nullptr); @@ -61,7 +61,7 @@ private: std::shared_ptr stack_limits; Episode episode; GameMode mode; - uint8_t difficulty; + Difficulty difficulty; uint8_t section_id; std::shared_ptr rare_item_set; std::shared_ptr armor_random_set; diff --git a/src/Lobby.cc b/src/Lobby.cc index 06f19505..033131a5 100644 --- a/src/Lobby.cc +++ b/src/Lobby.cc @@ -220,7 +220,7 @@ void Lobby::create_item_creator(Version logic_version) { s->rare_item_set(logic_version, this->quest), s->armor_random_set, s->tool_random_set, - s->weapon_random_sets.at(this->difficulty), + s->weapon_random_set(this->difficulty), s->tekker_adjustment_set, s->item_parameter_table(logic_version), s->item_stack_limits(logic_version), diff --git a/src/Lobby.hh b/src/Lobby.hh index 4a7fbda9..5f2c2a2a 100644 --- a/src/Lobby.hh +++ b/src/Lobby.hh @@ -119,7 +119,7 @@ struct Lobby : public std::enable_shared_from_this { uint8_t override_section_id = 0xFF; Episode episode = Episode::NONE; GameMode mode = GameMode::NORMAL; - uint8_t difficulty = 0; // 0-3 + Difficulty difficulty = Difficulty::NORMAL; float base_exp_multiplier = 1.0f; float exp_share_multiplier = 0.5f; float challenge_exp_multiplier = 1.0f; diff --git a/src/Main.cc b/src/Main.cc index 54d473cd..d50503b0 100644 --- a/src/Main.cc +++ b/src/Main.cc @@ -1610,7 +1610,7 @@ Action a_disassemble_quest_script( if (!args.get("decompressed")) { data = prs_decompress(data); } - uint8_t override_language = args.get("language", 0xFF); + Language override_language = static_cast(args.get("language", 0xFF)); bool reassembly_mode = args.get("reassembly"); bool use_qedit_names = args.get("qedit"); string result = disassemble_quest_script(data.data(), data.size(), version, override_language, reassembly_mode, use_qedit_names); @@ -2080,7 +2080,7 @@ Action a_download_files( remote_port, args.get("output-dir", true), version, - args.get("language"), + static_cast(args.get("language")), key, phosg::random_object(), serial_number, @@ -2173,12 +2173,10 @@ Action a_convert_rare_item_set( } else if (output_filename_lower.ends_with(".html")) { Version cli_version = get_cli_version(args, Version::BB_V4); bool is_v1 = ::is_v1(cli_version); - static const array modes = {GameMode::NORMAL, GameMode::BATTLE, GameMode::CHALLENGE, GameMode::SOLO}; - for (GameMode mode : modes) { - static const array episodes = {Episode::EP1, Episode::EP2, Episode::EP4}; - for (Episode episode : episodes) { - for (size_t difficulty = 0; difficulty < (is_v1 ? 3 : 4); difficulty++) { - if (!rs->has_entries_for_game_config(mode, episode, difficulty)) { + for (GameMode mode : ALL_GAME_MODES_V4) { + for (Episode episode : ALL_EPISODES_V4) { + for (Difficulty difficulty : ALL_DIFFICULTIES_V234) { + if ((is_v1 && (difficulty == Difficulty::ULTIMATE)) || (!rs->has_entries_for_game_config(mode, episode, difficulty))) { continue; } auto item_name_index = s->item_name_index(cli_version); @@ -2594,7 +2592,7 @@ Action a_generate_ep3_cards_html( shared_ptr text_english; try { - text_english = s->text_index->get(Version::GC_EP3, 1); + text_english = s->text_index->get(Version::GC_EP3, Language::ENGLISH); } catch (const out_of_range&) { } @@ -2833,12 +2831,13 @@ Action a_show_ep3_maps( phosg::log_info_f("{} maps", map_ids.size()); for (const auto& [map_number, map] : map_ids) { const auto& vms = map->all_versions(); - for (size_t language = 0; language < vms.size(); language++) { - if (!vms[language]) { + for (size_t lang_index = 0; lang_index < vms.size(); lang_index++) { + if (!vms[lang_index]) { continue; } - string map_s = vms[language]->map->str(s->ep3_card_index.get(), language); - phosg::fwrite_fmt(stdout, "({}) {}\n", char_for_language_code(language), map_s); + Language language = static_cast(lang_index); + string map_s = vms[lang_index]->map->str(s->ep3_card_index.get(), language); + phosg::fwrite_fmt(stdout, "({}) {}\n", char_for_language(language), map_s); } } }); @@ -2891,7 +2890,7 @@ Action a_check_supermaps( for (const auto& it : s->supermap_for_free_play_key) { auto episode = static_cast((it.first >> 28) & 7); auto mode = static_cast((it.first >> 26) & 3); - uint8_t difficulty = (it.first >> 24) & 3; + Difficulty difficulty = static_cast((it.first >> 24) & 3); uint8_t floor = (it.first >> 16) & 0xFF; uint8_t layout = (it.first >> 8) & 0xFF; uint8_t entities = (it.first >> 0) & 0xFF; @@ -2924,12 +2923,9 @@ Action a_check_supermaps( // Generate MapStates for a few random variations for (size_t z = 0; z < 0x20; z++) { - static const array episodes = {Episode::EP1, Episode::EP2, Episode::EP4}; - static const array modes = {GameMode::NORMAL, GameMode::BATTLE, GameMode::CHALLENGE, GameMode::SOLO}; - - Episode episode = episodes[phosg::random_object() % episodes.size()]; - GameMode mode = modes[phosg::random_object() % modes.size()]; - uint8_t difficulty = phosg::random_object() % 4; + Episode episode = ALL_EPISODES_V4[phosg::random_object() % ALL_EPISODES_V4.size()]; + GameMode mode = ALL_GAME_MODES_V4[phosg::random_object() % ALL_GAME_MODES_V4.size()]; + Difficulty difficulty = static_cast(phosg::random_object() % 4); uint8_t event = phosg::random_object() % 8; uint32_t random_seed = phosg::random_object(); phosg::fwrite_fmt(stderr, "FREE MAP STATE TEST: {} {} {}\n", @@ -3014,7 +3010,7 @@ Action a_check_supermaps( auto map_state = make_shared( 0, - phosg::random_object() & 3, + static_cast(phosg::random_object() & 3), 0, phosg::random_object(), MapState::DEFAULT_RARE_ENEMIES, diff --git a/src/Map.cc b/src/Map.cc index 38fe97ed..b41973bb 100644 --- a/src/Map.cc +++ b/src/Map.cc @@ -3398,7 +3398,7 @@ string MapFile::name_for_enemy_type(uint16_t type, Version version, uint8_t area string MapFile::ObjectSetEntry::str(Version version, uint8_t area) const { string name_str = MapFile::name_for_object_type(this->base_type, version, area); - return std::format("[ObjectSetEntry type={:04X} \"{}\" floor={:04X} group={:04X} room={:04X} a3={:04X} x={:g} y={:g} z={:g} x_angle={:08X} y_angle={:08X} z_angle={:08X} params=[{:g} {:g} {:g} {:08X} {:08X} {:08X}] unused={:08X}]", + return std::format("[ObjectSetEntry type={:04X} \"{}\" floor={:04X} group={:04X} room={:04X} a3={:04X} x={:g} y={:g} z={:g} x_angle={:08X} y_angle={:08X} z_angle={:08X} params=[{:g} {:g} {:g} {:08X} {:08X} {:08X}]]", this->base_type, name_str, this->floor, @@ -3416,8 +3416,7 @@ string MapFile::ObjectSetEntry::str(Version version, uint8_t area) const { this->param3, this->param4, this->param5, - this->param6, - this->unused); + this->param6); } uint64_t MapFile::ObjectSetEntry::semantic_hash(uint8_t floor) const { @@ -3438,7 +3437,7 @@ uint64_t MapFile::ObjectSetEntry::semantic_hash(uint8_t floor) const { string MapFile::EnemySetEntry::str(Version version, uint8_t area) const { auto type_name = MapFile::name_for_enemy_type(this->base_type, version, area); - return std::format("[EnemySetEntry type={:04X} \"{}\" num_children={:04X} floor={:04X} room={:04X} wave_number={:04X} wave_number2={:04X} a1={:04X} x={:g} y={:g} z={:g} x_angle={:08X} y_angle={:08X} z_angle={:08X} params=[{:g} {:g} {:g} {:g} {:g} {:04X} {:04X}] unused={:08X}]", + return std::format("[EnemySetEntry type={:04X} \"{}\" num_children={:04X} floor={:04X} room={:04X} wave_number={:04X} wave_number2={:04X} a1={:04X} x={:g} y={:g} z={:g} x_angle={:08X} y_angle={:08X} z_angle={:08X} params=[{:g} {:g} {:g} {:g} {:g} {:04X} {:04X}]]", this->base_type, type_name, this->num_children, @@ -3459,8 +3458,7 @@ string MapFile::EnemySetEntry::str(Version version, uint8_t area) const { this->param4, this->param5, this->param6, - this->param7, - this->unused); + this->param7); } uint64_t MapFile::EnemySetEntry::semantic_hash(uint8_t floor) const { @@ -5951,7 +5949,7 @@ size_t MapState::EventIterator::num_entities_on_current_floor() const { MapState::MapState( uint64_t lobby_or_session_id, - uint8_t difficulty, + Difficulty difficulty, uint8_t event, uint32_t random_seed, std::shared_ptr bb_rare_rates, @@ -6001,7 +5999,7 @@ MapState::MapState( MapState::MapState( uint64_t lobby_or_session_id, - uint8_t difficulty, + Difficulty difficulty, uint8_t event, uint32_t random_seed, std::shared_ptr bb_rare_rates, @@ -6048,6 +6046,12 @@ void MapState::index_super_map(const FloorConfig& fc, shared_ptrall_enemies()) { auto& ene_st = this->enemy_states.emplace_back(make_shared()); + if (ene->alias_enemy_index_delta) { + ene_st->alias_ene_st = this->enemy_states.at((this->enemy_states.size() - 1) + ene->alias_enemy_index_delta); + if (ene_st->alias_ene_st->alias_ene_st) { + throw std::runtime_error("target for enemy state alias is itself an alias"); + } + } if (ene->child_index == 0) { this->enemy_set_states.emplace_back(ene_st); } @@ -6059,12 +6063,12 @@ void MapState::index_super_map(const FloorConfig& fc, shared_ptrtype) { case EnemyType::DARK_FALZ_3: - type = ((this->difficulty == 0) && (ene->alias_enemy_index_delta == 0)) + type = ((this->difficulty == Difficulty::NORMAL) && (ene->alias_enemy_index_delta == 0)) ? EnemyType::DARK_FALZ_2 : EnemyType::DARK_FALZ_3; break; case EnemyType::DARVANT: - type = (this->difficulty == 3) ? EnemyType::DARVANT_ULTIMATE : EnemyType::DARVANT; + type = (this->difficulty == Difficulty::ULTIMATE) ? EnemyType::DARVANT_ULTIMATE : EnemyType::DARVANT; break; default: type = ene->type; @@ -6687,7 +6691,7 @@ void MapState::print(FILE* stream) const { phosg::fwrite_fmt(stream, "BB rare rates: {}\n", rare_rates_str); phosg::fwrite_fmt(stream, "Base indexes:\n"); - phosg::fwrite_fmt(stream, " FL DCTE----------- DCPR----------- DCV1----------- DCV2----------- PCTE----------- PCV2----------- GCTE----------- GCV3----------- GCEP3TE-------- GCEP3---------- XBV3----------- BBV4-----------\n"); + phosg::fwrite_fmt(stream, " FL DC-NTE--------- DC-11-2000----- DC-V1---------- DC-V2---------- PC-NTE--------- PC-V2---------- GC-NTE--------- GC-V3---------- GC-EP3-NTE----- GC-EP3--------- XB-V3---------- BB-V4----------\n"); phosg::fwrite_fmt(stream, " FL KST EST ESS EVT KST EST ESS EVT KST EST ESS EVT KST EST ESS EVT KST EST ESS EVT KST EST ESS EVT KST EST ESS EVT KST EST ESS EVT KST EST ESS EVT KST EST ESS EVT KST EST ESS EVT KST EST ESS EVT\n"); for (size_t floor = 0; floor < this->floor_config_entries.size(); floor++) { auto fc = this->floor_config_entries[floor]; @@ -6723,9 +6727,12 @@ void MapState::print(FILE* stream) const { } phosg::fwrite_fmt(stream, "Enemies:\n"); - phosg::fwrite_fmt(stream, " FL ENEID DCTE----- DCPR----- DCV1----- DCV2----- PCTE----- PCV2----- GCTE----- GCV3----- EP3TE---- GCEP3---- XBV3----- BBV4----- ENEMY\n"); + phosg::fwrite_fmt(stream, " FL ENEID ALIAS DCTE----- DCPR----- DCV1----- DCV2----- PCTE----- PCV2----- GCTE----- GCV3----- EP3TE---- GCEP3---- XBV3----- BBV4----- ENEMY\n"); for (const auto& ene_st : this->enemy_states) { - phosg::fwrite_fmt(stream, " {:02X} E-{:03X}", ene_st->super_ene->floor, ene_st->e_id); + std::string alias_str = ene_st->super_ene->alias_enemy_index_delta + ? std::format("E-{:03X}", ene_st->e_id + ene_st->super_ene->alias_enemy_index_delta) + : "-----"; + phosg::fwrite_fmt(stream, " {:02X} E-{:03X} {}", ene_st->super_ene->floor, ene_st->e_id, alias_str); const auto& fc = this->floor_config(ene_st->super_ene->floor); for (Version v : ALL_NON_PATCH_VERSIONS) { const auto& ene_v = ene_st->super_ene->version(v); diff --git a/src/Map.hh b/src/Map.hh index e592e40b..d7a7e4c5 100644 --- a/src/Map.hh +++ b/src/Map.hh @@ -187,7 +187,7 @@ public: /* 34 */ le_int32_t param4 = 0; /* 38 */ le_int32_t param5 = 0; /* 3C */ le_int32_t param6 = 0; - /* 40 */ le_uint32_t unused = 0; // Reserved for pointer in client's memory; unused by server + /* 40 */ le_uint32_t unused_obj_ptr = 0; // Reserved for pointer in client's memory; unused by server /* 44 */ uint64_t semantic_hash(uint8_t floor) const; @@ -215,7 +215,7 @@ public: /* 3C */ le_float param5 = 0.0f; /* 40 */ le_int16_t param6 = 0; /* 42 */ le_int16_t param7 = 0; - /* 44 */ le_uint32_t unused = 0; // Reserved for pointer in client's memory; unused by server + /* 44 */ le_uint32_t unused_obj_ptr = 0; // Reserved for pointer in client's memory; unused by server /* 48 */ uint64_t semantic_hash(uint8_t floor) const; @@ -710,6 +710,7 @@ public: }; struct EnemyState { + std::shared_ptr alias_ene_st; // Null for most enemies std::shared_ptr super_ene; enum Flag { LAST_HIT_MASK = 0x0003, @@ -745,7 +746,7 @@ public: inline void set_mericarand_variant_flag(Version version) { this->mericarand_variant_flags |= (1 << static_cast(version)); } - inline EnemyType type(Version version, Episode episode, uint8_t event) const { + inline EnemyType type(Version version, Episode episode, Difficulty difficulty, uint8_t event) const { if (this->super_ene->type == EnemyType::MERICARAND) { if (this->is_rare(version)) { return ((this->mericarand_variant_flags >> static_cast(version)) & 1) @@ -754,6 +755,10 @@ public: } else { return EnemyType::MERICAROL; } + } else if (this->super_ene->type == EnemyType::DARK_FALZ_3) { + return ((difficulty == Difficulty::NORMAL) && (this->super_ene->alias_enemy_index_delta == 0)) + ? EnemyType::DARK_FALZ_2 + : EnemyType::DARK_FALZ_3; } else { return this->is_rare(version) ? type_definition_for_enemy(this->super_ene->type).rare_type(episode, event, this->super_ene->floor) @@ -874,7 +879,7 @@ public: phosg::PrefixedLogger log; std::vector floor_config_entries; - uint8_t difficulty = 0; + Difficulty difficulty = Difficulty::NORMAL; uint8_t event = 0; uint32_t random_seed = 0; std::shared_ptr bb_rare_rates; @@ -889,7 +894,7 @@ public: // Constructor for free play MapState( uint64_t lobby_or_session_id, - uint8_t difficulty, + Difficulty difficulty, uint8_t event, uint32_t random_seed, // For client-matched rare enemies (non-BB) std::shared_ptr bb_rare_rates, @@ -898,7 +903,7 @@ public: // Constructor for quests MapState( uint64_t lobby_or_session_id, - uint8_t difficulty, + Difficulty difficulty, uint8_t event, uint32_t random_seed, // For client-matched rare enemies (non-BB) std::shared_ptr bb_rare_rates, diff --git a/src/PatchDownloadSession.cc b/src/PatchDownloadSession.cc index ec41db4d..204cab66 100644 --- a/src/PatchDownloadSession.cc +++ b/src/PatchDownloadSession.cc @@ -61,7 +61,7 @@ asio::awaitable PatchDownloadSession::run() { this->io_context, std::move(sock), this->version, - 1, + Language::ENGLISH, netloc_str, this->show_command_data ? phosg::TerminalFormat::FG_GREEN : phosg::TerminalFormat::END, this->show_command_data ? phosg::TerminalFormat::FG_YELLOW : phosg::TerminalFormat::END); diff --git a/src/PlayerInventory.hh b/src/PlayerInventory.hh index e75032d6..06321f50 100644 --- a/src/PlayerInventory.hh +++ b/src/PlayerInventory.hh @@ -116,7 +116,7 @@ struct PlayerInventoryT { /* 0000 */ uint8_t num_items = 0; /* 0001 */ uint8_t hp_from_materials = 0; /* 0002 */ uint8_t tp_from_materials = 0; - /* 0003 */ uint8_t language = 0; + /* 0003 */ Language language = Language::JAPANESE; /* 0004 */ parray, 30> items; /* 034C */ @@ -265,14 +265,14 @@ struct PlayerInventoryT { // issue - its inventory format matches the rest of the versions. this->hp_from_materials = 0; this->tp_from_materials = 0; - this->language = 0; + this->language = Language::JAPANESE; } else if ((v != Version::PC_NTE) && (v != Version::PC_V2)) { - if (this->language > 4) { - this->language = 0; + if (static_cast(this->language) > 4) { + this->language = Language::JAPANESE; } } else { - if (this->language > 7) { - this->language = 0; + if (static_cast(this->language) > 7) { + this->language = Language::JAPANESE; } } diff --git a/src/PlayerSubordinates.cc b/src/PlayerSubordinates.cc index 2a191b8c..8fbaf383 100644 --- a/src/PlayerSubordinates.cc +++ b/src/PlayerSubordinates.cc @@ -50,7 +50,7 @@ void GuildCardBB::clear() { this->team_name.clear(); this->description.clear(); this->present = 0; - this->language = 0; + this->language = Language::JAPANESE; this->section_id = 0; this->char_class = 0; } @@ -218,11 +218,11 @@ PlayerRecordsChallengeBB::PlayerRecordsChallengeBB(const PlayerRecordsChallengeD grave_x(rec.grave_x), grave_y(rec.grave_y), grave_z(rec.grave_z), - grave_team(rec.grave_team.decode(), 1), - grave_message(rec.grave_message.decode(), 1), + grave_team(rec.grave_team.decode(), Language::ENGLISH), + grave_message(rec.grave_message.decode(), Language::ENGLISH), unknown_m5(0), unknown_t6(0), - rank_title(rec.rank_title.decode(), 1), + rank_title(rec.rank_title.decode(), Language::ENGLISH), unknown_l7(0) {} PlayerRecordsChallengeBB::PlayerRecordsChallengeBB(const PlayerRecordsChallengePC& rec) @@ -242,11 +242,11 @@ PlayerRecordsChallengeBB::PlayerRecordsChallengeBB(const PlayerRecordsChallengeP grave_x(rec.grave_x), grave_y(rec.grave_y), grave_z(rec.grave_z), - grave_team(rec.grave_team.decode(), 1), - grave_message(rec.grave_message.decode(), 1), + grave_team(rec.grave_team.decode(), Language::ENGLISH), + grave_message(rec.grave_message.decode(), Language::ENGLISH), unknown_m5(0), unknown_t6(0), - rank_title(rec.rank_title.decode(), 1), + rank_title(rec.rank_title.decode(), Language::ENGLISH), unknown_l7(0) {} PlayerRecordsChallengeBB::operator PlayerRecordsChallengeDC() const { diff --git a/src/PlayerSubordinates.hh b/src/PlayerSubordinates.hh index 72c03fc5..22cfb592 100644 --- a/src/PlayerSubordinates.hh +++ b/src/PlayerSubordinates.hh @@ -328,7 +328,7 @@ struct PlayerDispDataDCPCV3T { this->visual.enforce_lobby_join_limits_for_version(v); } - PlayerDispDataBB to_bb(uint8_t to_language, uint8_t from_language) const; + PlayerDispDataBB to_bb(Language to_language, Language from_language) const; } __attribute__((packed)); using PlayerDispDataDCPCV3 = PlayerDispDataDCPCV3T; using PlayerDispDataDCPCV3BE = PlayerDispDataDCPCV3T; @@ -361,7 +361,7 @@ struct PlayerDispDataBB { } template - PlayerDispDataDCPCV3T to_dcpcv3(uint8_t to_language, uint8_t from_language) const { + PlayerDispDataDCPCV3T to_dcpcv3(Language to_language, Language from_language) const { PlayerDispDataDCPCV3T ret; ret.stats = this->stats; ret.visual = this->visual; @@ -377,7 +377,7 @@ struct PlayerDispDataBB { } __packed_ws__(PlayerDispDataBB, 0x190); template -PlayerDispDataBB PlayerDispDataDCPCV3T::to_bb(uint8_t to_language, uint8_t from_language) const { +PlayerDispDataBB PlayerDispDataDCPCV3T::to_bb(Language to_language, Language from_language) const { PlayerDispDataBB bb; bb.stats = this->stats; bb.visual = this->visual; @@ -398,7 +398,7 @@ struct GuildCardDCNTE { /* 20 */ pstring description; /* 68 */ parray unused2; /* 77 */ uint8_t present = 0; - /* 78 */ uint8_t language = 0; + /* 78 */ Language language = Language::JAPANESE; /* 79 */ uint8_t section_id = 0; /* 7A */ uint8_t char_class = 0; /* 7B */ @@ -413,7 +413,7 @@ struct GuildCardDC { /* 20 */ pstring description; /* 68 */ parray unused2; /* 79 */ uint8_t present = 0; - /* 7A */ uint8_t language = 0; + /* 7A */ Language language = Language::JAPANESE; /* 7B */ uint8_t section_id = 0; /* 7C */ uint8_t char_class = 0; /* 7D */ @@ -428,7 +428,7 @@ struct GuildCardPC { /* 08 */ pstring name; /* 38 */ pstring description; /* EC */ uint8_t present = 0; - /* ED */ uint8_t language = 0; + /* ED */ Language language = Language::JAPANESE; /* EE */ uint8_t section_id = 0; /* EF */ uint8_t char_class = 0; /* F0 */ @@ -456,7 +456,7 @@ struct GuildCardGCT { /* 08:08 */ pstring name; /* 20:20 */ pstring description; /* A0:8C */ uint8_t present = 0; - /* A1:8D */ uint8_t language = 0; + /* A1:8D */ Language language = Language::JAPANESE; /* A2:8E */ uint8_t section_id = 0; /* A3:8F */ uint8_t char_class = 0; /* A4:90 */ @@ -480,7 +480,7 @@ struct GuildCardXB { /* 0010 */ pstring name; /* 0028 */ pstring description; /* 0228 */ uint8_t present = 0; - /* 0229 */ uint8_t language = 0; + /* 0229 */ Language language = Language::JAPANESE; /* 022A */ uint8_t section_id = 0; /* 022B */ uint8_t char_class = 0; /* 022C */ @@ -494,7 +494,7 @@ struct GuildCardBB { /* 0034 */ pstring team_name; /* 0054 */ pstring description; /* 0104 */ uint8_t present = 0; - /* 0105 */ uint8_t language = 0; + /* 0105 */ Language language = Language::JAPANESE; /* 0106 */ uint8_t section_id = 0; /* 0107 */ uint8_t char_class = 0; /* 0108 */ @@ -776,13 +776,13 @@ struct PlayerRecordsChallengeBB { grave_x(rec.grave_x), grave_y(rec.grave_y), grave_z(rec.grave_z), - grave_team(rec.grave_team.decode(), 1), - grave_message(rec.grave_message.decode(), 1), + grave_team(rec.grave_team.decode(), Language::ENGLISH), + grave_message(rec.grave_message.decode(), Language::ENGLISH), unknown_m5(rec.unknown_m5), ep1_online_award_state(rec.ep1_online_award_state), ep2_online_award_state(rec.ep2_online_award_state), ep1_offline_award_state(rec.ep1_offline_award_state), - rank_title(rec.rank_title.decode(), 1), + rank_title(rec.rank_title.decode(), Language::ENGLISH), unknown_l7(rec.unknown_l7) { for (size_t z = 0; z < std::min(this->unknown_t6.size(), rec.unknown_t6.size()); z++) { this->unknown_t6[z] = rec.unknown_t6[z]; @@ -810,8 +810,8 @@ struct PlayerRecordsChallengeBB { ret.grave_x = this->grave_x; ret.grave_y = this->grave_y; ret.grave_z = this->grave_z; - ret.grave_team.encode(this->grave_team.decode(), 1); - ret.grave_message.encode(this->grave_message.decode(), 1); + ret.grave_team.encode(this->grave_team.decode(), Language::ENGLISH); + ret.grave_message.encode(this->grave_message.decode(), Language::ENGLISH); ret.unknown_m5 = this->unknown_m5; for (size_t z = 0; z < std::min(ret.unknown_t6.size(), this->unknown_t6.size()); z++) { ret.unknown_t6[z] = this->unknown_t6[z]; @@ -819,7 +819,7 @@ struct PlayerRecordsChallengeBB { ret.ep1_online_award_state = this->ep1_online_award_state; ret.ep2_online_award_state = this->ep2_online_award_state; ret.ep1_offline_award_state = this->ep1_offline_award_state; - ret.rank_title.encode(this->rank_title.decode(), 1); + ret.rank_title.encode(this->rank_title.decode(), Language::ENGLISH); ret.unknown_l7 = this->unknown_l7; return ret; } @@ -856,31 +856,31 @@ check_struct_size(PlayerRecordsBattle, 0x18); check_struct_size(PlayerRecordsBattleBE, 0x18); template -DestT convert_player_disp_data(const SrcT&, uint8_t, uint8_t) { +DestT convert_player_disp_data(const SrcT&, Language, Language) { static_assert(phosg::always_false::v, "unspecialized convert_player_disp_data should never be called"); } template <> -inline PlayerDispDataDCPCV3 convert_player_disp_data(const PlayerDispDataDCPCV3& src, uint8_t, uint8_t) { +inline PlayerDispDataDCPCV3 convert_player_disp_data(const PlayerDispDataDCPCV3& src, Language, Language) { return src; } template <> inline PlayerDispDataDCPCV3 convert_player_disp_data( - const PlayerDispDataBB& src, uint8_t to_language, uint8_t from_language) { + const PlayerDispDataBB& src, Language to_language, Language from_language) { return src.to_dcpcv3(to_language, from_language); } template <> inline PlayerDispDataBB convert_player_disp_data( - const PlayerDispDataDCPCV3& src, uint8_t to_language, uint8_t from_language) { + const PlayerDispDataDCPCV3& src, Language to_language, Language from_language) { return src.to_bb(to_language, from_language); } template <> inline PlayerDispDataBB convert_player_disp_data( - const PlayerDispDataBB& src, uint8_t, uint8_t) { + const PlayerDispDataBB& src, Language, Language) { return src; } @@ -916,21 +916,28 @@ struct QuestFlagsForDifficulty { struct QuestFlags { parray data; - inline bool get(uint8_t difficulty, uint16_t flag_index) const { - return this->data[difficulty].get(flag_index); + inline QuestFlagsForDifficulty& for_difficulty(Difficulty difficulty) { + return this->data[static_cast(difficulty)]; } - inline void set(uint8_t difficulty, uint16_t flag_index) { - this->data[difficulty].set(flag_index); + inline const QuestFlagsForDifficulty& for_difficulty(Difficulty difficulty) const { + return this->data[static_cast(difficulty)]; } - inline void clear(uint8_t difficulty, uint16_t flag_index) { - this->data[difficulty].clear(flag_index); + + inline bool get(Difficulty difficulty, uint16_t flag_index) const { + return this->for_difficulty(difficulty).get(flag_index); } - inline void update_all(uint8_t difficulty, bool set) { - this->data[difficulty].update_all(set); + inline void set(Difficulty difficulty, uint16_t flag_index) { + this->for_difficulty(difficulty).set(flag_index); + } + inline void clear(Difficulty difficulty, uint16_t flag_index) { + this->for_difficulty(difficulty).clear(flag_index); + } + inline void update_all(Difficulty difficulty, bool set) { + this->for_difficulty(difficulty).update_all(set); } inline void update_all(bool set) { - for (size_t z = 0; z < 4; z++) { - this->update_all(z, set); + for (Difficulty difficulty : ALL_DIFFICULTIES_V234) { + this->update_all(difficulty, set); } } } __packed_ws__(QuestFlags, 0x200); diff --git a/src/ProxyCommands.cc b/src/ProxyCommands.cc index e9f39794..af06dec5 100644 --- a/src/ProxyCommands.cc +++ b/src/ProxyCommands.cc @@ -872,7 +872,13 @@ static asio::awaitable SC_6x60_6xA2(shared_ptr c, Channel G_SpecializableItemDropRequest_6xA2 cmd = normalize_drop_request(msg.data.data(), msg.data.size()); auto rec = reconcile_drop_request_with_map( - c, cmd, c->proxy_session->lobby_episode, c->proxy_session->lobby_event, c->proxy_session->map_state, false); + c, + cmd, + c->proxy_session->lobby_episode, + c->proxy_session->lobby_difficulty, + c->proxy_session->lobby_event, + c->proxy_session->map_state, + false); ItemCreator::DropResult res; if (rec.obj_st) { @@ -1509,7 +1515,7 @@ static asio::awaitable S_65_67_68_EB(shared_ptr c, Channe c->proxy_session->is_in_game = false; c->proxy_session->is_in_quest = false; c->floor = 0x0F; - c->proxy_session->lobby_difficulty = 0; + c->proxy_session->lobby_difficulty = Difficulty::NORMAL; c->proxy_session->lobby_section_id = 0; c->proxy_session->lobby_mode = GameMode::NORMAL; c->proxy_session->lobby_episode = Episode::EP1; @@ -1682,7 +1688,7 @@ static asio::awaitable S_64(shared_ptr c, Channel::Messag } else { c->proxy_session->lobby_event = 0; - c->proxy_session->lobby_difficulty = 0; + c->proxy_session->lobby_difficulty = Difficulty::NORMAL; c->proxy_session->lobby_section_id = c->character_file()->disp.visual.section_id; c->proxy_session->lobby_mode = GameMode::NORMAL; c->proxy_session->lobby_random_seed = phosg::random_object(); @@ -1758,7 +1764,7 @@ static asio::awaitable S_E8(shared_ptr c, Channel::Messag c->proxy_session->is_in_game = true; c->proxy_session->is_in_quest = false; c->proxy_session->lobby_event = cmd.event; - c->proxy_session->lobby_difficulty = 0; + c->proxy_session->lobby_difficulty = Difficulty::NORMAL; c->proxy_session->lobby_section_id = cmd.section_id; c->proxy_session->lobby_mode = GameMode::NORMAL; c->proxy_session->lobby_random_seed = 0; @@ -1848,7 +1854,7 @@ static asio::awaitable C_98(shared_ptr c, Channel::Messag c->proxy_session->is_in_game = false; c->proxy_session->is_in_quest = false; c->proxy_session->lobby_event = 0; - c->proxy_session->lobby_difficulty = 0; + c->proxy_session->lobby_difficulty = Difficulty::NORMAL; c->proxy_session->lobby_section_id = 0; c->proxy_session->lobby_episode = Episode::EP1; c->proxy_session->lobby_mode = GameMode::NORMAL; diff --git a/src/ProxySession.cc b/src/ProxySession.cc index 4d7a8a2a..b9bf589e 100644 --- a/src/ProxySession.cc +++ b/src/ProxySession.cc @@ -31,7 +31,7 @@ void ProxySession::set_drop_mode( s->rare_item_set(version, nullptr), s->armor_random_set, s->tool_random_set, - s->weapon_random_sets.at(this->lobby_difficulty), + s->weapon_random_set(this->lobby_difficulty), s->tekker_adjustment_set, s->item_parameter_table(version), s->item_stack_limits(version), diff --git a/src/ProxySession.hh b/src/ProxySession.hh index a6bed480..57df9e18 100644 --- a/src/ProxySession.hh +++ b/src/ProxySession.hh @@ -28,7 +28,7 @@ struct ProxySession { uint32_t guild_card_number = 0; uint64_t xb_user_id = 0; std::string name; - uint8_t language = 0; + Language language = Language::JAPANESE; uint8_t section_id = 0; uint8_t char_class = 0; }; @@ -38,7 +38,7 @@ struct ProxySession { bool is_in_quest = false; uint8_t leader_client_id = 0; uint8_t lobby_event = 0; - uint8_t lobby_difficulty = 0; + Difficulty lobby_difficulty = Difficulty::NORMAL; uint8_t lobby_section_id = 0; GameMode lobby_mode = GameMode::NORMAL; Episode lobby_episode = Episode::EP1; diff --git a/src/Quest.cc b/src/Quest.cc index af97c9bd..7acce9cc 100644 --- a/src/Quest.cc +++ b/src/Quest.cc @@ -203,7 +203,7 @@ void VersionedQuest::assert_valid() const { if (this->version == Version::UNKNOWN) { throw runtime_error("version is not set"); } - if (this->language == 0xFF) { + if (this->language == Language::UNKNOWN) { throw runtime_error("language is not set"); } switch (this->meta.episode) { @@ -290,7 +290,7 @@ string VersionedQuest::pvr_filename() const { string VersionedQuest::xb_filename() const { return std::format("quest{}_{}.dat", - this->meta.quest_number, static_cast(tolower(char_for_language_code(this->language)))); + this->meta.quest_number, static_cast(tolower(char_for_language(this->language)))); } string VersionedQuest::encode_qst() const { @@ -301,7 +301,7 @@ string VersionedQuest::encode_qst() const { files.emplace(std::format("quest{}.pvr", this->meta.quest_number), this->pvr_contents); } string xb_filename = std::format("quest{}_{}.dat", - this->meta.quest_number, static_cast(tolower(char_for_language_code(language)))); + this->meta.quest_number, static_cast(tolower(char_for_language(language)))); return encode_qst_file(files, this->meta.name, this->meta.quest_number, xb_filename, this->version, this->is_dlq_encoded); } @@ -315,7 +315,7 @@ phosg::JSON Quest::json() const { for (const auto& [_, vq] : this->versions) { versions_json.emplace_back(phosg::JSON::dict({ {"Version", phosg::name_for_enum(vq->version)}, - {"Language", name_for_language_code(vq->language)}, + {"Language", name_for_language(vq->language)}, {"Name", vq->meta.name}, {"ShortDescription", vq->meta.short_description}, {"LongDescription", vq->meta.long_description}, @@ -331,16 +331,18 @@ phosg::JSON Quest::json() const { }); } -uint32_t Quest::versions_key(Version v, uint8_t language) { - return (static_cast(v) << 8) | language; +uint32_t Quest::versions_key(Version v, Language language) { + return (static_cast(v) << 8) | static_cast(language); } -const string& Quest::name_for_language(uint8_t language) const { - if (!this->names_by_language.at(language).empty()) { - return this->names_by_language[language]; +const string& Quest::name_for_language(Language language) const { + size_t lang_index = static_cast(language); + if (!this->names_by_language.at(lang_index).empty()) { + return this->names_by_language[lang_index]; } - if (!this->names_by_language[1].empty()) { - return this->names_by_language[1]; + constexpr size_t english_lang_index = static_cast(Language::ENGLISH); + if (!this->names_by_language[english_lang_index].empty()) { + return this->names_by_language[english_lang_index]; } for (const string& name : this->names_by_language) { if (!name.empty()) { @@ -354,7 +356,8 @@ void Quest::add_version(shared_ptr vq) { this->meta.assert_compatible(vq->meta); this->versions.emplace(this->versions_key(vq->version, vq->language), vq); - auto& name_by_language = this->names_by_language.at(vq->language); + size_t lang_index = static_cast(vq->language); + auto& name_by_language = this->names_by_language.at(lang_index); if (name_by_language.empty()) { name_by_language = vq->meta.name; } @@ -369,7 +372,7 @@ std::shared_ptr Quest::get_supermap(int64_t random_seed) const { bool any_map_file_present = false; array, NUM_VERSIONS> map_files; for (Version v : ALL_NON_PATCH_VERSIONS) { - auto vq = this->version(v, 1); + auto vq = this->version(v, Language::ENGLISH); if (vq && vq->map_file) { auto map_file = vq->map_file; if (map_file->has_random_sections()) { @@ -398,17 +401,17 @@ std::shared_ptr Quest::get_supermap(int64_t random_seed) const { return supermap; } -bool Quest::has_version(Version v, uint8_t language) const { +bool Quest::has_version(Version v, Language language) const { return this->versions.count(this->versions_key(v, language)); } bool Quest::has_version_any_language(Version v) const { - uint32_t k = this->versions_key(v, 0); + uint32_t k = this->versions_key(v, Language::JAPANESE); auto it = this->versions.lower_bound(k); return ((it != this->versions.end()) && ((it->first & 0xFF00) == k)); } -shared_ptr Quest::version(Version v, uint8_t language) const { +shared_ptr Quest::version(Version v, Language language) const { // Return the requested version, if it exists try { return this->versions.at(this->versions_key(v, language)); @@ -417,13 +420,13 @@ shared_ptr Quest::version(Version v, uint8_t language) con // Return the English version, if it exists try { - return this->versions.at(this->versions_key(v, 1)); + return this->versions.at(this->versions_key(v, Language::ENGLISH)); } catch (const out_of_range&) { } // Return the first language, if it exists - auto it = this->versions.lower_bound(this->versions_key(v, 0)); - if ((it == this->versions.end()) || ((it->first & 0xFF00) != this->versions_key(v, 0))) { + auto it = this->versions.lower_bound(this->versions_key(v, Language::JAPANESE)); + if ((it == this->versions.end()) || ((it->first & 0xFF00) != this->versions_key(v, Language::JAPANESE))) { return nullptr; } return it->second; @@ -646,7 +649,7 @@ QuestIndex::QuestIndex( if (language_token.size() != 1) { throw runtime_error("language token is not a single character"); } - vq->language = language_code_for_char(language_token[0]); + vq->language = language_for_char(language_token[0]); } auto bin_decompressed = prs_decompress(*entry.data); @@ -777,29 +780,27 @@ QuestIndex::QuestIndex( auto q_it = this->quests_by_number.find(vq->meta.quest_number); if (q_it != this->quests_by_number.end()) { q_it->second->add_version(vq); - static_game_data_log.debug_f("({}) Added {} {} version of quest {} ({}) with floors {}", + static_game_data_log.debug_f("({}) Added {} {} version of quest {} ({})", filenames_str, phosg::name_for_enum(vq->version), - char_for_language_code(vq->language), + char_for_language(vq->language), vq->meta.quest_number, - vq->meta.name, - phosg::format_data_string(vq->meta.area_for_floor.data(), 0x12)); + vq->meta.name); } else { auto q = make_shared(vq); this->quests_by_number.emplace(vq->meta.quest_number, q); this->quests_by_name.emplace(vq->meta.name, q); this->quests_by_category_id_and_number[q->meta.category_id].emplace(vq->meta.quest_number, q); - static_game_data_log.debug_f("({}) Created {} {} quest {} ({}) ({}, {} ({}), {}) with floors {}", + static_game_data_log.debug_f("({}) Created {} {} quest {} ({}) ({}, {} ({}), {})", filenames_str, phosg::name_for_enum(vq->version), - char_for_language_code(vq->language), + char_for_language(vq->language), vq->meta.quest_number, vq->meta.name, name_for_episode(vq->meta.episode), category_name, vq->meta.category_id, - vq->meta.joinable ? "joinable" : "not joinable", - phosg::format_data_string(vq->meta.area_for_floor.data(), 0x12)); + vq->meta.joinable ? "joinable" : "not joinable"); } } catch (const exception& e) { static_game_data_log.warning_f("({}) Failed to index quest file: {}", basename, e.what()); @@ -935,7 +936,7 @@ string encode_download_quest_data(const string& compressed_data, size_t decompre return data; } -shared_ptr VersionedQuest::create_download_quest(uint8_t override_language) const { +shared_ptr VersionedQuest::create_download_quest(Language override_language) const { // The download flag needs to be set in the bin header, or else the client // will ignore it when scanning for download quests in an offline game. To set // this flag, we need to decompress the quest's .bin file, set the flag, then @@ -958,7 +959,7 @@ shared_ptr VersionedQuest::create_download_quest(uint8_t overrid if (decompressed_bin.size() < sizeof(PSOQuestHeaderDC)) { throw runtime_error("bin file is too small for header"); } - if (override_language != 0xFF) { + if (override_language != Language::UNKNOWN) { reinterpret_cast(data_ptr)->language = override_language; } break; @@ -967,7 +968,7 @@ shared_ptr VersionedQuest::create_download_quest(uint8_t overrid if (decompressed_bin.size() < sizeof(PSOQuestHeaderPC)) { throw runtime_error("bin file is too small for header"); } - if (override_language != 0xFF) { + if (override_language != Language::UNKNOWN) { reinterpret_cast(data_ptr)->language = override_language; } break; @@ -977,7 +978,7 @@ shared_ptr VersionedQuest::create_download_quest(uint8_t overrid if (decompressed_bin.size() < sizeof(PSOQuestHeaderGC)) { throw runtime_error("bin file is too small for header"); } - if (override_language != 0xFF) { + if (override_language != Language::UNKNOWN) { reinterpret_cast(data_ptr)->language = override_language; } break; diff --git a/src/Quest.hh b/src/Quest.hh index f42c435c..9e796ddc 100644 --- a/src/Quest.hh +++ b/src/Quest.hh @@ -72,7 +72,7 @@ struct VersionedQuest { // Most of these default values are intentionally invalid; we use these // values to check if each field was parsed during quest indexing. Version version = Version::UNKNOWN; - uint8_t language = 0xFF; + Language language = Language::UNKNOWN; std::shared_ptr bin_contents; std::shared_ptr dat_contents; std::shared_ptr map_file; @@ -86,7 +86,7 @@ struct VersionedQuest { std::string pvr_filename() const; std::string xb_filename() const; - std::shared_ptr create_download_quest(uint8_t override_language = 0xFF) const; + std::shared_ptr create_download_quest(Language override_language = Language::UNKNOWN) const; std::string encode_qst() const; }; @@ -107,14 +107,14 @@ struct Quest { std::shared_ptr get_supermap(int64_t random_seed) const; - const std::string& name_for_language(uint8_t language) const; + const std::string& name_for_language(Language language) const; void add_version(std::shared_ptr vq); - bool has_version(Version v, uint8_t language) const; + bool has_version(Version v, Language language) const; bool has_version_any_language(Version v) const; - std::shared_ptr version(Version v, uint8_t language) const; + std::shared_ptr version(Version v, Language language) const; - static uint32_t versions_key(Version v, uint8_t language); + static uint32_t versions_key(Version v, Language language); }; struct QuestIndex { diff --git a/src/QuestMetadata.cc b/src/QuestMetadata.cc index ed8ce76c..ce6f46ec 100644 --- a/src/QuestMetadata.cc +++ b/src/QuestMetadata.cc @@ -69,7 +69,7 @@ void QuestMetadata::assert_compatible(const QuestMetadata& other) const { if (this->challenge_difficulty != other.challenge_difficulty) { throw runtime_error(std::format( "quest version has different challenge difficulty (existing: {}, new: {})", - this->challenge_difficulty, other.challenge_difficulty)); + name_for_difficulty(this->challenge_difficulty), name_for_difficulty(other.challenge_difficulty))); } for (size_t z = 0; z < this->area_for_floor.size(); z++) { const auto& this_fa = this->area_for_floor[z]; @@ -150,7 +150,7 @@ phosg::JSON QuestMetadata::json() const { {"BattleRules", this->battle_rules ? this->battle_rules->json() : phosg::JSON(nullptr)}, {"ChallengeTemplateIndex", (this->challenge_template_index >= 0) ? this->challenge_template_index : phosg::JSON(nullptr)}, {"ChallengeEXPMultiplier", (this->challenge_exp_multiplier >= 0) ? this->challenge_exp_multiplier : phosg::JSON(nullptr)}, - {"ChallengeDifficulty", (this->challenge_difficulty >= 0) ? this->challenge_difficulty : phosg::JSON(nullptr)}, + {"ChallengeDifficulty", (this->challenge_difficulty != Difficulty::UNKNOWN) ? name_for_difficulty(this->challenge_difficulty) : phosg::JSON(nullptr)}, {"DescriptionFlag", this->description_flag}, {"AvailableExpression", this->available_expression ? this->available_expression->str() : phosg::JSON(nullptr)}, {"EnabledExpression", this->available_expression ? this->available_expression->str() : phosg::JSON(nullptr)}, diff --git a/src/QuestMetadata.hh b/src/QuestMetadata.hh index 0e6a2768..f0c68436 100644 --- a/src/QuestMetadata.hh +++ b/src/QuestMetadata.hh @@ -28,7 +28,7 @@ struct QuestMetadata { std::shared_ptr battle_rules; ssize_t challenge_template_index = -1; float challenge_exp_multiplier = -1.0f; - int8_t challenge_difficulty = -1; + Difficulty challenge_difficulty = Difficulty::UNKNOWN; uint8_t description_flag = 0x00; std::shared_ptr available_expression; std::shared_ptr enabled_expression; diff --git a/src/QuestScript.cc b/src/QuestScript.cc index 5862e1b5..e09dd394 100644 --- a/src/QuestScript.cc +++ b/src/QuestScript.cc @@ -82,8 +82,8 @@ static const char* name_for_header_episode_number(uint8_t episode) { } } -static TextEncoding encoding_for_language(uint8_t language) { - return (language ? TextEncoding::ISO8859 : TextEncoding::SJIS); +static TextEncoding encoding_for_language(Language language) { + return ((language == Language::JAPANESE) ? TextEncoding::SJIS : TextEncoding::ISO8859); } static string escape_string(const string& data, TextEncoding encoding = TextEncoding::UTF8) { @@ -2970,7 +2970,7 @@ std::string disassemble_quest_script( const void* data, size_t size, Version version, - uint8_t override_language, + Language override_language, bool reassembly_mode, bool use_qedit_names) { phosg::StringReader r(data, size); @@ -2980,14 +2980,14 @@ std::string disassemble_quest_script( bool use_wstrs = false; size_t code_offset = 0; size_t function_table_offset = 0; - uint8_t language; + Language language; switch (version) { case Version::DC_NTE: { const auto& header = r.get(); code_offset = header.code_offset; function_table_offset = header.function_table_offset; - language = 0; - lines.emplace_back(".name " + escape_string(header.name.decode(0))); + language = Language::JAPANESE; + lines.emplace_back(".name " + escape_string(header.name.decode(Language::JAPANESE))); break; } case Version::DC_11_2000: @@ -2996,15 +2996,15 @@ std::string disassemble_quest_script( const auto& header = r.get(); code_offset = header.code_offset; function_table_offset = header.function_table_offset; - if (override_language != 0xFF) { + if (override_language != Language::UNKNOWN) { language = override_language; - } else if (header.language < 5) { + } else if (static_cast(header.language) < 5) { language = header.language; } else { - language = 1; + language = Language::ENGLISH; } lines.emplace_back(std::format(".quest_num {}", header.quest_number)); - lines.emplace_back(std::format(".language {}", header.language)); + lines.emplace_back(std::format(".language {}", char_for_language(header.language))); lines.emplace_back(".name " + escape_string(header.name.decode(language))); lines.emplace_back(".short_desc " + escape_string(header.short_description.decode(language))); lines.emplace_back(".long_desc " + escape_string(header.long_description.decode(language))); @@ -3016,15 +3016,15 @@ std::string disassemble_quest_script( const auto& header = r.get(); code_offset = header.code_offset; function_table_offset = header.function_table_offset; - if (override_language != 0xFF) { + if (override_language != Language::UNKNOWN) { language = override_language; - } else if (header.language < 8) { + } else if (static_cast(header.language) < 8) { language = header.language; } else { - language = 1; + language = Language::ENGLISH; } lines.emplace_back(std::format(".quest_num {}", header.quest_number)); - lines.emplace_back(std::format(".language {}", header.language)); + lines.emplace_back(std::format(".language {}", char_for_language(header.language))); lines.emplace_back(".name " + escape_string(header.name.decode(language))); lines.emplace_back(".short_desc " + escape_string(header.short_description.decode(language))); lines.emplace_back(".long_desc " + escape_string(header.long_description.decode(language))); @@ -3038,15 +3038,15 @@ std::string disassemble_quest_script( const auto& header = r.get(); code_offset = header.code_offset; function_table_offset = header.function_table_offset; - if (override_language != 0xFF) { + if (override_language != Language::UNKNOWN) { language = override_language; - } else if (header.language < 5) { + } else if (static_cast(header.language) < 5) { language = header.language; } else { - language = 1; + language = Language::ENGLISH; } lines.emplace_back(std::format(".quest_num {}", header.quest_number)); - lines.emplace_back(std::format(".language {}", header.language)); + lines.emplace_back(std::format(".language {}", char_for_language(header.language))); lines.emplace_back(".name " + escape_string(header.name.decode(language))); lines.emplace_back(".short_desc " + escape_string(header.short_description.decode(language))); lines.emplace_back(".long_desc " + escape_string(header.long_description.decode(language))); @@ -3057,10 +3057,10 @@ std::string disassemble_quest_script( const auto& header = r.get(); code_offset = header.code_offset; function_table_offset = header.function_table_offset; - if (override_language != 0xFF) { + if (override_language != Language::UNKNOWN) { language = override_language; } else { - language = 1; + language = Language::ENGLISH; } lines.emplace_back(std::format(".quest_num {}", header.quest_number)); lines.emplace_back(std::format(".episode {}", name_for_header_episode_number(header.episode))); @@ -3335,7 +3335,7 @@ std::string disassemble_quest_script( } else { string s = cmd_r.get_cstr(); if (def->flags & F_PUSH_ARG) { - arg_stack_values.emplace_back(language ? tt_8859_to_utf8(s) : tt_sega_sjis_to_utf8(s)); + arg_stack_values.emplace_back((language == Language::JAPANESE) ? tt_sega_sjis_to_utf8(s) : tt_8859_to_utf8(s)); } dasm_arg = escape_string(s, encoding_for_language(language)); } @@ -4008,7 +4008,7 @@ AssembledQuestScript assemble_quest_script( string quest_short_desc; string quest_long_desc; int64_t quest_num = -1; - uint8_t quest_language = 1; + Language quest_language = Language::ENGLISH; Episode quest_episode = Episode::EP1; uint8_t quest_max_players = 4; bool quest_joinable = false; @@ -4033,7 +4033,11 @@ AssembledQuestScript assemble_quest_script( } else if (line.text.starts_with(".quest_num ")) { quest_num = stoul(line.text.substr(11), nullptr, 0); } else if (line.text.starts_with(".language ")) { - quest_language = stoul(line.text.substr(10), nullptr, 0); + auto code = line.text.substr(10); + if (code.size() != 1) { + throw runtime_error(".language directive argument is invalid"); + } + quest_language = language_for_char(code[0]); } else if (line.text.starts_with(".episode ")) { quest_episode = episode_for_token_name(line.text.substr(9)); } else if (line.text.starts_with(".max_players ")) { @@ -4322,7 +4326,7 @@ AssembledQuestScript assemble_quest_script( case Version::GC_EP3_NTE: case Version::GC_EP3: case Version::XB_V3: - code_w.write(bin ? text : (quest_language ? tt_utf8_to_8859(text) : tt_utf8_to_sega_sjis(text))); + code_w.write(bin ? text : ((quest_language == Language::JAPANESE) ? tt_utf8_to_sega_sjis(text) : tt_utf8_to_8859(text))); code_w.put_u8(0); break; case Version::PC_NTE: @@ -4571,7 +4575,7 @@ AssembledQuestScript assemble_quest_script( header.code_offset = sizeof(header); header.function_table_offset = sizeof(header) + code_w.size(); header.size = header.function_table_offset + function_table.size() * sizeof(function_table[0]); - header.name.encode(quest_name, 0); + header.name.encode(quest_name, Language::JAPANESE); w.put(header); break; } @@ -4662,7 +4666,7 @@ AssembledQuestScript assemble_quest_script( } void populate_quest_metadata_from_script( - QuestMetadata& meta, const void* data, size_t size, Version version, uint8_t language) { + QuestMetadata& meta, const void* data, size_t size, Version version, Language language) { phosg::StringReader r(data, size); uint32_t code_offset = r.size(); uint32_t function_table_offset = r.size(); @@ -5192,7 +5196,10 @@ void populate_quest_metadata_from_script( break; case 0xF824: // set_cmode_difficulty - meta.challenge_difficulty = get_single_int32_arg(); + meta.challenge_difficulty = static_cast(get_single_int32_arg()); + if (static_cast(meta.challenge_difficulty) > 3) { + throw std::runtime_error("invalid challenge mode difficulty"); + } // phosg::fwrite_fmt(stderr, ">>> Trace: meta.challenge_difficulty = {}\n", meta.challenge_difficulty); break; diff --git a/src/QuestScript.hh b/src/QuestScript.hh index d6caa6c1..54fb22d4 100644 --- a/src/QuestScript.hh +++ b/src/QuestScript.hh @@ -38,7 +38,7 @@ struct PSOQuestHeaderDC { // Same format for DC v1 and v2 /* 0008 */ le_uint32_t size = 0; /* 000C */ le_uint16_t unknown_a1 = 0; /* 000E */ le_uint16_t unknown_a2 = 0; - /* 0010 */ uint8_t language = 0; + /* 0010 */ Language language = Language::JAPANESE; /* 0011 */ uint8_t unknown_a3 = 0; /* 0012 */ le_uint16_t quest_number = 0; // 0xFFFF for challenge quests /* 0014 */ pstring name; @@ -53,7 +53,7 @@ struct PSOQuestHeaderPC { /* 0008 */ le_uint32_t size = 0; /* 000C */ le_uint16_t unknown_a1 = 0; /* 000E */ le_uint16_t unknown_a2 = 0; - /* 0010 */ uint8_t language = 0; + /* 0010 */ Language language = Language::JAPANESE; /* 0011 */ uint8_t unknown_a3 = 0; /* 0012 */ le_uint16_t quest_number = 0; // 0xFFFF for challenge quests /* 0014 */ pstring name; @@ -70,7 +70,7 @@ struct PSOQuestHeaderGC { /* 0008 */ le_uint32_t size = 0; /* 000C */ le_uint16_t unknown_a1 = 0; /* 000E */ le_uint16_t unknown_a2 = 0; - /* 0010 */ uint8_t language = 0; + /* 0010 */ Language language = Language::JAPANESE; /* 0011 */ uint8_t unknown_a3 = 0; // Note: The GC client byteswaps this field, then loads it as a byte, so // technically the high byte of this is what the client uses as the quest @@ -109,7 +109,7 @@ std::string disassemble_quest_script( const void* data, size_t size, Version version, - uint8_t override_language = 0xFF, + Language override_language = Language::UNKNOWN, bool reassembly_mode = false, bool use_qedit_names = false); @@ -117,7 +117,7 @@ struct AssembledQuestScript { std::string data; int64_t quest_number = -1; Version version = Version::UNKNOWN; - uint8_t language = 0xFF; + Language language = Language::UNKNOWN; Episode episode = Episode::NONE; bool joinable = false; uint8_t max_players = 0x00; @@ -130,4 +130,4 @@ AssembledQuestScript assemble_quest_script( const std::vector& script_include_directories, const std::vector& native_include_directories); -void populate_quest_metadata_from_script(QuestMetadata& meta, const void* data, size_t size, Version version, uint8_t language); +void populate_quest_metadata_from_script(QuestMetadata& meta, const void* data, size_t size, Version version, Language language); diff --git a/src/RareItemSet.cc b/src/RareItemSet.cc index 1e6d9ad1..1b8c5a51 100644 --- a/src/RareItemSet.cc +++ b/src/RareItemSet.cc @@ -236,12 +236,11 @@ RareItemSet::SpecCollection RareItemSet::ParsedRELData::as_collection() const { } RareItemSet::RareItemSet(const AFSArchive& afs, bool is_v1) { - const array modes = {GameMode::NORMAL, GameMode::BATTLE, GameMode::CHALLENGE, GameMode::SOLO}; - for (GameMode mode : modes) { - for (size_t difficulty = 0; difficulty < 4; difficulty++) { + for (GameMode mode : ALL_GAME_MODES_V4) { + for (Difficulty difficulty : ALL_DIFFICULTIES_V234) { for (size_t section_id = 0; section_id < 10; section_id++) { try { - size_t index = difficulty * 10 + section_id; + size_t index = static_cast(difficulty) * 10 + section_id; ParsedRELData rel(afs.get_reader(index), false, is_v1); this->collections.emplace( this->key_for_params(mode, Episode::EP1, difficulty, section_id), @@ -253,7 +252,7 @@ RareItemSet::RareItemSet(const AFSArchive& afs, bool is_v1) { } } -string RareItemSet::gsl_entry_name_for_table(GameMode mode, Episode episode, uint8_t difficulty, uint8_t section_id) { +string RareItemSet::gsl_entry_name_for_table(GameMode mode, Episode episode, Difficulty difficulty, uint8_t section_id) { return std::format("ItemRT{}{}{}{}.rel", ((mode == GameMode::CHALLENGE) ? "c" : ""), ((episode == Episode::EP2) ? "l" : ""), @@ -262,11 +261,9 @@ string RareItemSet::gsl_entry_name_for_table(GameMode mode, Episode episode, uin } RareItemSet::RareItemSet(const GSLArchive& gsl, bool is_big_endian) { - const array episodes = {Episode::EP1, Episode::EP2}; - const array modes = {GameMode::NORMAL, GameMode::BATTLE, GameMode::CHALLENGE, GameMode::SOLO}; - for (GameMode mode : modes) { - for (Episode episode : episodes) { - for (size_t difficulty = 0; difficulty < 4; difficulty++) { + for (GameMode mode : ALL_GAME_MODES_V23) { + for (Episode episode : ALL_EPISODES_V3) { + for (Difficulty difficulty : ALL_DIFFICULTIES_V234) { for (size_t section_id = 0; section_id < 10; section_id++) { try { string filename = this->gsl_entry_name_for_table(mode, episode, difficulty, section_id); @@ -285,15 +282,15 @@ RareItemSet::RareItemSet(const GSLArchive& gsl, bool is_big_endian) { RareItemSet::RareItemSet(const string& rel_data, bool is_big_endian) { // Tables are 0x280 bytes in size in this format, laid out sequentially phosg::StringReader r(rel_data); - array episodes = {Episode::EP1, Episode::EP2, Episode::EP4}; - for (size_t ep_index = 0; ep_index < episodes.size(); ep_index++) { - for (size_t difficulty = 0; difficulty < 4; difficulty++) { + for (Episode episode : ALL_EPISODES_V4) { + for (Difficulty difficulty : ALL_DIFFICULTIES_V234) { for (size_t section_id = 0; section_id < 10; section_id++) { try { - size_t index = (ep_index * 40) + difficulty * 10 + section_id; + size_t ep_index = (episode == Episode::EP1) ? 0 : ((episode == Episode::EP2) ? 1 : 2); + size_t index = (ep_index * 40) + static_cast(difficulty) * 10 + section_id; ParsedRELData rel(r.sub(0x280 * index, 0x280), is_big_endian, false); this->collections.emplace( - this->key_for_params(GameMode::NORMAL, episodes[ep_index], difficulty, section_id), + this->key_for_params(GameMode::NORMAL, episode, difficulty, section_id), rel.as_collection()); } catch (const out_of_range&) { } @@ -314,9 +311,9 @@ RareItemSet::RareItemSet(const phosg::JSON& json, shared_ptras_dict()) { - static const unordered_map difficulty_keys( - {{"Normal", 0}, {"Hard", 1}, {"VeryHard", 2}, {"Ultimate", 3}}); - uint8_t difficulty = difficulty_keys.at(difficulty_it.first); + static const unordered_map difficulty_keys( + {{"Normal", Difficulty::NORMAL}, {"Hard", Difficulty::HARD}, {"VeryHard", Difficulty::VERY_HARD}, {"Ultimate", Difficulty::ULTIMATE}}); + Difficulty difficulty = difficulty_keys.at(difficulty_it.first); for (const auto& section_id_it : difficulty_it.second->as_dict()) { uint8_t section_id = section_id_for_name(section_id_it.first); @@ -385,7 +382,10 @@ RareItemSet::RareItemSet(const phosg::JSON& json, shared_ptr files; - for (uint8_t difficulty = 0; difficulty < (is_v1 ? 3 : 4); difficulty++) { + for (Difficulty difficulty : ALL_DIFFICULTIES_V234) { + if (is_v1 && (difficulty == Difficulty::ULTIMATE)) { + continue; + } for (uint8_t section_id = 0; section_id < 10; section_id++) { ParsedRELData rel(this->get_collection(GameMode::NORMAL, Episode::EP1, difficulty, section_id)); files.emplace_back(rel.serialize(false, is_v1)); @@ -397,9 +397,8 @@ std::string RareItemSet::serialize_afs(bool is_v1) const { std::string RareItemSet::serialize_gsl(bool big_endian) const { unordered_map files; - static const std::array episodes = {Episode::EP1, Episode::EP2}; - for (Episode episode : episodes) { - for (uint8_t difficulty = 0; difficulty < 4; difficulty++) { + for (Episode episode : ALL_EPISODES_V3) { + for (Difficulty difficulty : ALL_DIFFICULTIES_V234) { for (uint8_t section_id = 0; section_id < 10; section_id++) { try { string filename = this->gsl_entry_name_for_table(GameMode::NORMAL, episode, difficulty, section_id); @@ -412,7 +411,7 @@ std::string RareItemSet::serialize_gsl(bool big_endian) const { } } - for (uint8_t difficulty = 0; difficulty < 4; difficulty++) { + for (Difficulty difficulty : ALL_DIFFICULTIES_V234) { for (uint8_t section_id = 0; section_id < 10; section_id++) { try { string filename = this->gsl_entry_name_for_table(GameMode::CHALLENGE, Episode::EP1, difficulty, section_id); @@ -429,7 +428,7 @@ std::string RareItemSet::serialize_gsl(bool big_endian) const { string RareItemSet::serialize_html( GameMode mode, Episode episode, - uint8_t difficulty, + Difficulty difficulty, shared_ptr name_index, shared_ptr common_item_set) const { @@ -762,7 +761,7 @@ string RareItemSet::serialize_html( specs_lists[section_id] = this->get_enemy_specs(mode, episode, difficulty, section_id, rt_index); } const auto& type_def = type_definition_for_enemy(type); - const char* name = (difficulty == 3 && type_def.ultimate_name) ? type_def.ultimate_name : type_def.in_game_name; + const char* name = (difficulty == Difficulty::ULTIMATE && type_def.ultimate_name) ? type_def.ultimate_name : type_def.in_game_name; add_specs_row(&type_def, name, false, specs_lists); } for (uint8_t floor : zone_type.floors) { @@ -785,13 +784,11 @@ string RareItemSet::serialize_html( phosg::JSON RareItemSet::json(shared_ptr name_index) const { auto modes_dict = phosg::JSON::dict(); - static const array modes = {GameMode::NORMAL, GameMode::BATTLE, GameMode::CHALLENGE, GameMode::SOLO}; - for (const auto& mode : modes) { + for (const auto& mode : ALL_GAME_MODES_V4) { auto episodes_dict = phosg::JSON::dict(); - static const array episodes = {Episode::EP1, Episode::EP2, Episode::EP4}; - for (const auto& episode : episodes) { + for (const auto& episode : ALL_EPISODES_V4) { auto difficulty_dict = phosg::JSON::dict(); - for (uint8_t difficulty = 0; difficulty < 4; difficulty++) { + for (const auto& difficulty : ALL_DIFFICULTIES_V234) { auto section_id_dict = phosg::JSON::dict(); for (uint8_t section_id = 0; section_id < 10; section_id++) { auto collection_dict = phosg::JSON::dict(); @@ -884,7 +881,7 @@ void RareItemSet::print_collection( FILE* stream, GameMode mode, Episode episode, - uint8_t difficulty, + Difficulty difficulty, uint8_t section_id, shared_ptr name_index) const { const SpecCollection* collection; @@ -928,11 +925,9 @@ void RareItemSet::print_collection( } void RareItemSet::print_all_collections(FILE* stream, std::shared_ptr name_index) const { - static const array modes = {GameMode::NORMAL, GameMode::BATTLE, GameMode::CHALLENGE, GameMode::SOLO}; - static const array episodes = {Episode::EP1, Episode::EP2, Episode::EP4}; - for (GameMode mode : modes) { - for (Episode episode : episodes) { - for (uint8_t difficulty = 0; difficulty < 4; difficulty++) { + for (GameMode mode : ALL_GAME_MODES_V4) { + for (Episode episode : ALL_EPISODES_V4) { + for (Difficulty difficulty : ALL_DIFFICULTIES_V234) { for (uint8_t section_id = 0; section_id < 10; section_id++) { try { this->print_collection(stream, mode, episode, difficulty, section_id, name_index); @@ -945,7 +940,7 @@ void RareItemSet::print_all_collections(FILE* stream, std::shared_ptr RareItemSet::get_enemy_specs( - GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t rt_index) const { + GameMode mode, Episode episode, Difficulty difficulty, uint8_t secid, uint8_t rt_index) const { try { return this->get_collection(mode, episode, difficulty, secid).rt_index_to_specs.at(rt_index); } catch (const out_of_range&) { @@ -955,7 +950,7 @@ std::vector RareItemSet::get_enemy_specs( } std::vector RareItemSet::get_box_specs( - GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t area_norm) const { + GameMode mode, Episode episode, Difficulty difficulty, uint8_t secid, uint8_t area_norm) const { try { return this->get_collection(mode, episode, difficulty, secid).box_area_norm_to_specs.at(area_norm); } catch (const out_of_range&) { @@ -964,7 +959,7 @@ std::vector RareItemSet::get_box_specs( } } -bool RareItemSet::has_entries_for_game_config(GameMode mode, Episode episode, uint8_t difficulty) const { +bool RareItemSet::has_entries_for_game_config(GameMode mode, Episode episode, Difficulty difficulty) const { for (uint8_t section_id = 0; section_id < 10; section_id++) { if (this->collections.count(this->key_for_params(mode, episode, difficulty, section_id))) { return true; @@ -974,19 +969,19 @@ bool RareItemSet::has_entries_for_game_config(GameMode mode, Episode episode, ui } const RareItemSet::SpecCollection& RareItemSet::get_collection( - GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid) const { + GameMode mode, Episode episode, Difficulty difficulty, uint8_t secid) const { return this->collections.at(this->key_for_params(mode, episode, difficulty, secid)); } -uint16_t RareItemSet::key_for_params(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid) { - if (difficulty > 3) { +uint16_t RareItemSet::key_for_params(GameMode mode, Episode episode, Difficulty difficulty, uint8_t secid) { + if (static_cast(difficulty) > 3) { throw logic_error("incorrect difficulty"); } if (secid > 10) { throw logic_error("incorrect section id"); } - uint16_t key = ((difficulty & 3) << 4) | (secid & 0x0F); + uint16_t key = ((static_cast(difficulty) & 3) << 4) | (secid & 0x0F); switch (mode) { case GameMode::NORMAL: break; diff --git a/src/RareItemSet.hh b/src/RareItemSet.hh index 7d234941..e04df69c 100644 --- a/src/RareItemSet.hh +++ b/src/RareItemSet.hh @@ -34,18 +34,18 @@ public: ~RareItemSet() = default; std::vector get_enemy_specs( - GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t rt_index) const; + GameMode mode, Episode episode, Difficulty difficulty, uint8_t secid, uint8_t rt_index) const; std::vector get_box_specs( - GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid, uint8_t area_norm) const; + GameMode mode, Episode episode, Difficulty difficulty, uint8_t secid, uint8_t area_norm) const; - bool has_entries_for_game_config(GameMode mode, Episode episode, uint8_t difficulty) const; + bool has_entries_for_game_config(GameMode mode, Episode episode, Difficulty difficulty) const; std::string serialize_afs(bool is_v1) const; std::string serialize_gsl(bool big_endian) const; std::string serialize_html( GameMode mode, Episode episode, - uint8_t difficulty, + Difficulty difficulty, std::shared_ptr name_index = nullptr, std::shared_ptr common_item_set = nullptr) const; phosg::JSON json(std::shared_ptr name_index = nullptr) const; @@ -56,7 +56,7 @@ public: FILE* stream, GameMode mode, Episode episode, - uint8_t difficulty, + Difficulty difficulty, uint8_t section_id, std::shared_ptr name_index = nullptr) const; void print_all_collections(FILE* stream, std::shared_ptr name_index = nullptr) const; @@ -113,10 +113,10 @@ protected: std::unordered_map collections; - const SpecCollection& get_collection(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid) const; + const SpecCollection& get_collection(GameMode mode, Episode episode, Difficulty difficulty, uint8_t secid) const; - static std::string gsl_entry_name_for_table(GameMode mode, Episode episode, uint8_t difficulty, uint8_t section_id); - static uint16_t key_for_params(GameMode mode, Episode episode, uint8_t difficulty, uint8_t secid); + static std::string gsl_entry_name_for_table(GameMode mode, Episode episode, Difficulty difficulty, uint8_t section_id); + static uint16_t key_for_params(GameMode mode, Episode episode, Difficulty difficulty, uint8_t secid); static uint32_t expand_rate(uint8_t pc); static uint8_t compress_rate(uint32_t probability); diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 5e8274dc..966c8f56 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -347,7 +347,7 @@ static asio::awaitable on_login_complete(shared_ptr c) { if (!q) { c->log.info_f("There is no quest to enable server function calls for specific version {:08X}", c->specific_version); } else if (q) { - auto vq = q->version(c->version(), 1); + auto vq = q->version(c->version(), Language::ENGLISH); if (vq) { c->set_flag(Client::Flag::HAS_SEND_FUNCTION_CALL); c->set_flag(Client::Flag::SEND_FUNCTION_CALL_ACTUALLY_RUNS_CODE); @@ -363,7 +363,7 @@ static asio::awaitable on_login_complete(shared_ptr c) { lobby_data.guild_card_number = c->login->account->account_id; send_command_t(c, 0x64, 0x01, cmd); } else { - c->log.info_f("Sending {} version of quest \"{}\"", char_for_language_code(vq->language), vq->meta.name); + c->log.info_f("Sending {} version of quest \"{}\"", name_for_language(vq->language), vq->meta.name); string bin_filename = vq->bin_filename(); string dat_filename = vq->dat_filename(); string xb_filename = vq->xb_filename(); @@ -613,7 +613,7 @@ static asio::awaitable on_04_U(shared_ptr c, Channel::Message& msg for (const auto& file : index->all_files()) { send_patch_change_to_directory(c, path_directories, file->path_directories); - S_FileChecksumRequest_Patch_0C req = {c->patch_file_checksum_requests.size(), {file->name, 1}}; + S_FileChecksumRequest_Patch_0C req = {c->patch_file_checksum_requests.size(), {file->name, Language::ENGLISH}}; c->channel->send(0x0C, 0x00, req); c->patch_file_checksum_requests.emplace_back(file); } @@ -667,7 +667,7 @@ asio::awaitable on_10_U(shared_ptr c, Channel::Message&) { if (req.needs_update()) { send_patch_change_to_directory(c, path_directories, req.file->path_directories); - S_OpenFile_Patch_06 open_cmd = {0, req.file->size, {req.file->name, 1}}; + S_OpenFile_Patch_06 open_cmd = {0, req.file->size, {req.file->name, Language::ENGLISH}}; c->channel->send(0x06, 0x00, open_cmd); for (size_t x = 0; x < req.file->chunk_crcs.size(); x++) { @@ -1490,7 +1490,7 @@ static asio::awaitable on_93_BB(shared_ptr c, Channel::Message& ms c->set_flag(Client::Flag::FORCE_ENGLISH_LANGUAGE_BB); } } - c->channel->language = c->check_flag(Client::Flag::FORCE_ENGLISH_LANGUAGE_BB) ? 1 : base_cmd.language; + c->channel->language = c->check_flag(Client::Flag::FORCE_ENGLISH_LANGUAGE_BB) ? Language::ENGLISH : base_cmd.language; if (base_cmd.menu_id == MenuID::LOBBY) { c->preferred_lobby_id = base_cmd.preferred_lobby_id; @@ -2237,7 +2237,7 @@ static asio::awaitable on_09(shared_ptr c, Channel::Message& msg) version_token, name_for_char_class(player->disp.visual.char_class), player->disp.stats.level + 1, - char_for_language_code(game_c->language())); + char_for_language(game_c->language())); } } } @@ -2471,7 +2471,7 @@ void set_lobby_quest(shared_ptr l, shared_ptr q, bool substi l->allowed_drop_modes = l->quest->meta.allowed_drop_modes; l->drop_mode = l->quest->meta.default_drop_mode; } - if (l->quest->meta.challenge_difficulty >= 0) { + if (l->quest->meta.challenge_difficulty != Difficulty::UNKNOWN) { l->difficulty = l->quest->meta.challenge_difficulty; } l->create_item_creator(); @@ -2491,7 +2491,7 @@ void set_lobby_quest(shared_ptr l, shared_ptr q, bool substi lc->channel->disconnect(); break; } - lc->log.info_f("Sending {} version of quest \"{}\"", char_for_language_code(vq->language), vq->meta.name); + lc->log.info_f("Sending {} version of quest \"{}\"", name_for_language(vq->language), vq->meta.name); string bin_filename = vq->bin_filename(); string dat_filename = vq->dat_filename(); @@ -2901,7 +2901,7 @@ static asio::awaitable on_10_ep3_download_quest_menu(shared_ptr c, auto map = s->ep3_download_map_index->get(item_id); auto vm = map->version(c->language()); auto name = vm->map->name.decode(vm->language); - string filename = std::format("m{:06}p_{:c}.bin", map->map_number, tolower(char_for_language_code(vm->language))); + string filename = std::format("m{:06}p_{:c}.bin", map->map_number, tolower(char_for_language(vm->language))); auto data = (c->version() == Version::GC_EP3_NTE) ? vm->trial_download() : vm->compressed(false); send_open_quest_file(c, name, filename, "", map->map_number, QuestFileType::EPISODE_3, data); co_return; @@ -4136,14 +4136,15 @@ static asio::awaitable on_DF_BB(shared_ptr c, Channel::Message& ms if (!l->quest) { throw runtime_error("challenge mode difficulty config command sent in non-challenge game"); } - if (static_cast(l->quest->meta.challenge_difficulty) != cmd.difficulty) { + Difficulty cmd_difficulty = static_cast(cmd.difficulty32.load()); + if (l->quest->meta.challenge_difficulty != cmd_difficulty) { throw runtime_error("incorrect difficulty level"); } - if (l->difficulty != cmd.difficulty) { - l->difficulty = cmd.difficulty; + if (l->difficulty != cmd_difficulty) { + l->difficulty = cmd_difficulty; l->create_item_creator(); } - l->log.info_f("(Challenge mode) Difficulty set to {:02X}", l->difficulty); + l->log.info_f("(Challenge mode) Difficulty set to {}", name_for_difficulty(l->difficulty)); break; } @@ -4491,7 +4492,7 @@ shared_ptr create_game_generic( const std::string& password, Episode episode, GameMode mode, - uint8_t difficulty, + Difficulty difficulty, bool allow_v1, shared_ptr watched_lobby, shared_ptr battle_player) { @@ -4503,7 +4504,7 @@ shared_ptr create_game_generic( throw invalid_argument("incorrect episode number"); } - if (difficulty > 3) { + if (static_cast(difficulty) > 3) { throw invalid_argument("incorrect difficulty level"); } @@ -4527,7 +4528,7 @@ shared_ptr create_game_generic( game->difficulty = difficulty; game->allowed_versions = s->compatibility_groups.at(static_cast(creator_c->version())); static_assert(NUM_VERSIONS == 14, "Don't forget to update the group compatibility restrictions"); - if (!allow_v1 || (difficulty > 2) || (mode == GameMode::CHALLENGE) || (mode == GameMode::SOLO)) { + if (!allow_v1 || (difficulty == Difficulty::ULTIMATE) || (mode == GameMode::CHALLENGE) || (mode == GameMode::SOLO)) { game->forbid_version(Version::DC_NTE); game->forbid_version(Version::DC_11_2000); game->forbid_version(Version::DC_V1); @@ -4701,7 +4702,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_by_difficulty.at(game->difficulty); + game->rare_enemy_rates = s->rare_enemy_rates(game->difficulty); } if (game->episode != Episode::EP3) { @@ -4736,7 +4737,7 @@ shared_ptr create_game_generic( if (quest_flag_rewrites && !quest_flag_rewrites->empty()) { IntegralExpression::Env env = { - .flags = &p->quest_flags.data.at(difficulty), + .flags = &p->quest_flags.for_difficulty(difficulty), .challenge_records = &p->challenge_records, .team = creator_c->team(), .num_players = 1, @@ -4788,7 +4789,15 @@ static asio::awaitable on_0C_C1_E7_EC(shared_ptr c, Channel::Messa shared_ptr game; if (is_pre_v1(c->version())) { const auto& cmd = check_size_t(msg.data); - game = create_game_generic(s, c, cmd.name.decode(c->language()), cmd.password.decode(c->language()), Episode::EP1, GameMode::NORMAL, 0, true); + game = create_game_generic( + s, + c, + cmd.name.decode(c->language()), + cmd.password.decode(c->language()), + Episode::EP1, + GameMode::NORMAL, + Difficulty::NORMAL, + true); } else { const auto& cmd = check_size_t(msg.data); @@ -4952,11 +4961,11 @@ static asio::awaitable on_6F(shared_ptr c, Channel::Message& msg) } catch (const out_of_range&) { throw std::logic_error("cannot find patch enable quest after it was previously found during login"); } - auto vq = q->version(is_ep3(c->version()) ? Version::GC_V3 : c->version(), 1); + auto vq = q->version(is_ep3(c->version()) ? Version::GC_V3 : c->version(), Language::ENGLISH); if (!vq) { throw std::logic_error("cannot find patch enable quest version after it was previously found during login"); } - c->log.info_f("Sending {} version of quest \"{}\"", char_for_language_code(vq->language), vq->meta.name); + c->log.info_f("Sending {} version of quest \"{}\"", name_for_language(vq->language), vq->meta.name); string bin_filename = vq->bin_filename(); string dat_filename = vq->dat_filename(); string xb_filename = vq->xb_filename(); diff --git a/src/ReceiveCommands.hh b/src/ReceiveCommands.hh index 23224d9c..a0452a9c 100644 --- a/src/ReceiveCommands.hh +++ b/src/ReceiveCommands.hh @@ -13,7 +13,7 @@ std::shared_ptr create_game_generic( const std::string& password = "", Episode episode = Episode::EP1, GameMode mode = GameMode::NORMAL, - uint8_t difficulty = 0, + Difficulty difficulty = Difficulty::NORMAL, bool allow_v1 = false, std::shared_ptr watched_lobby = nullptr, std::shared_ptr battle_player = nullptr); diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index b6433ed3..6e97b083 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -805,7 +805,7 @@ Parsed6x70Data::Parsed6x70Data( unknown_a6_nte(cmd.unknown_a6), bonus_hp_from_materials(0), bonus_tp_from_materials(0), - language(0), + language(Language::JAPANESE), player_tag(0x00010000), guild_card_number(guild_card_number), unknown_a6(0), @@ -829,7 +829,7 @@ Parsed6x70Data::Parsed6x70Data( Parsed6x70Data::Parsed6x70Data( const G_SyncPlayerDispAndInventory_DC112000_6x70& cmd, uint32_t guild_card_number, - uint8_t language, + Language language, Version from_version, bool from_client_customization) : from_version(from_version), @@ -922,8 +922,8 @@ Parsed6x70Data::Parsed6x70Data( this->floor = cmd.floor; this->xb_user_id = this->default_xb_user_id(); this->xb_unknown_a16 = cmd.unknown_a16; - this->name = cmd.name.decode(cmd.base.language); - this->visual.name.encode(this->name, cmd.base.language); + this->name = cmd.name.decode(this->language); + this->visual.name.encode(this->name, this->language); } G_SyncPlayerDispAndInventory_DCNTE_6x70 Parsed6x70Data::as_dc_nte(shared_ptr s) const { @@ -1051,7 +1051,7 @@ G_SyncPlayerDispAndInventory_XB_6x70 Parsed6x70Data::as_xb(shared_ptr s, uint8_t language) const { +G_SyncPlayerDispAndInventory_BB_6x70 Parsed6x70Data::as_bb(shared_ptr s, Language language) const { G_SyncPlayerDispAndInventory_BB_6x70 ret; ret.base = this->base_v1(true); ret.name.encode(this->name, language); @@ -1116,7 +1116,7 @@ Parsed6x70Data::Parsed6x70Data( attack_status_effect(base.attack_status_effect), defense_status_effect(base.defense_status_effect), unused_status_effect(base.unused_status_effect), - language(base.language), + language(static_cast(base.language32.load())), player_tag(base.player_tag), guild_card_number(guild_card_number), // Ignore the client's GC# unknown_a6(base.unknown_a6), @@ -1140,7 +1140,7 @@ G_6x70_Base_V1 Parsed6x70Data::base_v1(bool is_v3) const { ret.attack_status_effect = this->attack_status_effect; ret.defense_status_effect = this->defense_status_effect; ret.unused_status_effect = this->unused_status_effect; - ret.language = this->language; + ret.language32 = static_cast(this->language); ret.player_tag = this->player_tag; ret.guild_card_number = this->guild_card_number; ret.unknown_a6 = this->unknown_a6; @@ -2805,6 +2805,7 @@ DropReconcileResult reconcile_drop_request_with_map( shared_ptr c, G_SpecializableItemDropRequest_6xA2& cmd, Episode episode, + Difficulty difficulty, uint8_t event, shared_ptr map, bool mark_drop) { @@ -2857,7 +2858,7 @@ DropReconcileResult reconcile_drop_request_with_map( } else { if (map) { res.ene_st = map->enemy_state_for_index(version, cmd.floor, cmd.entity_index); - EnemyType type = res.ene_st->type(version, episode, event); + EnemyType type = res.ene_st->type(version, episode, difficulty, event); c->log.info_f("Drop check for E-{:03X} {}", res.ene_st->e_id, phosg::name_for_enum(type)); res.effective_rt_index = type_definition_for_enemy(type).rt_index; // rt_indexes in Episode 4 don't match those sent in the command; we just @@ -2921,7 +2922,7 @@ static asio::awaitable on_entity_drop_item_request(shared_ptr c, S // mode, so that we can correctly mark enemies and objects as having dropped // their items in persistent games. G_SpecializableItemDropRequest_6xA2 cmd = normalize_drop_request(msg.data, msg.size); - auto rec = reconcile_drop_request_with_map(c, cmd, l->episode, l->event, l->map_state, true); + auto rec = reconcile_drop_request_with_map(c, cmd, l->episode, l->difficulty, l->event, l->map_state, true); ServerDropMode drop_mode = l->drop_mode; switch (drop_mode) { @@ -3058,7 +3059,8 @@ static asio::awaitable on_set_quest_flag(shared_ptr c, SubcommandM co_return; } - uint16_t flag_num, difficulty, action; + uint16_t flag_num, action; + Difficulty difficulty; if (is_v1_or_v2(c->version()) && (c->version() != Version::GC_NTE)) { const auto& cmd = msg.check_size_t(); flag_num = cmd.flag; @@ -3068,12 +3070,12 @@ static asio::awaitable on_set_quest_flag(shared_ptr c, SubcommandM const auto& cmd = msg.check_size_t(); flag_num = cmd.flag; action = cmd.action; - difficulty = cmd.difficulty; + difficulty = static_cast(cmd.difficulty16.load()); } // The client explicitly checks action for both 0 and 1 - any other value // means no operation is performed. - if ((flag_num >= 0x400) || (difficulty > 3) || (action > 1)) { + if ((flag_num >= 0x400) || (static_cast(difficulty) > 3) || (action > 1)) { co_return; } bool should_set = (action == 0); @@ -3110,9 +3112,9 @@ static asio::awaitable on_set_quest_flag(shared_ptr c, SubcommandM // On Normal, Dark Falz does not have a third phase, so send the drop // request after the end of the second phase. On all other difficulty // levels, send it after the third phase. - if ((difficulty == 0) && (flag_num == 0x0035)) { + if ((difficulty == Difficulty::NORMAL) && (flag_num == 0x0035)) { boss_enemy_type = EnemyType::DARK_FALZ_2; - } else if ((difficulty != 0) && (flag_num == 0x0037)) { + } else if ((difficulty != Difficulty::NORMAL) && (flag_num == 0x0037)) { boss_enemy_type = EnemyType::DARK_FALZ_3; } } else if (is_ep2 && (flag_num == 0x0057) && (area == 0x0D)) { @@ -3426,13 +3428,20 @@ static asio::awaitable on_update_enemy_state(shared_ptr c, Subcomm } auto ene_st = l->map_state->enemy_state_for_index(c->version(), c->floor, cmd.enemy_index); uint32_t src_flags = is_big_endian(c->version()) ? bswap32(cmd.game_flags) : cmd.game_flags.load(); - if (l->difficulty == 3) { + if (l->difficulty == Difficulty::ULTIMATE) { src_flags = (src_flags & 0xFFFFFFC0) | (ene_st->game_flags & 0x0000003F); } ene_st->game_flags = src_flags; ene_st->total_damage = cmd.total_damage; ene_st->set_last_hit_by_client_id(c->lobby_client_id); l->log.info_f("E-{:03X} updated to damage={} game_flags={:08X}", ene_st->e_id, ene_st->total_damage, ene_st->game_flags); + auto& alias_ene_st = ene_st->alias_ene_st; + if (alias_ene_st) { + alias_ene_st->game_flags = ene_st->game_flags; + alias_ene_st->total_damage = ene_st->total_damage; + alias_ene_st->server_flags = ene_st->server_flags; + l->log.info_f("E-{:03X} updated via alias from E-{:03X}", alias_ene_st->e_id, ene_st->e_id); + } for (auto lc : l->clients) { if (lc && (lc != c)) { @@ -3485,7 +3494,7 @@ static asio::awaitable on_set_enemy_low_game_flags_ultimate(shared_ptrrequire_lobby(); - if (!l->is_game() || (l->difficulty != 3)) { + if (!l->is_game() || (l->difficulty != Difficulty::ULTIMATE)) { co_return; } @@ -3888,7 +3897,7 @@ static uint32_t base_exp_for_enemy_type( shared_ptr bp_index, EnemyType enemy_type, Episode current_episode, - uint8_t difficulty, + Difficulty difficulty, uint8_t area, bool is_solo) { // Always try the current episode first. If the current episode is Ep4, try @@ -3918,7 +3927,7 @@ static uint32_t base_exp_for_enemy_type( try { const auto& bp_table = bp_index->get_table(is_solo, episode); uint32_t bp_index = type_definition_for_enemy(enemy_type).bp_index; - return bp_table.stats[difficulty][bp_index].experience; + return bp_table.stats_for_index(difficulty, bp_index).experience; } catch (const out_of_range&) { } } @@ -3971,16 +3980,16 @@ static asio::awaitable on_steal_exp_bb(shared_ptr c, SubcommandMes co_return; } - auto type = ene_st->type(c->version(), l->episode, l->event); + auto type = ene_st->type(c->version(), l->episode, l->difficulty, l->event); uint32_t enemy_exp = base_exp_for_enemy_type( s->battle_params, type, l->episode, l->difficulty, ene_st->super_ene->floor, l->mode == GameMode::SOLO); // Note: The original code checks if special.type is 9, 10, or 11, and skips // applying the android bonus if so. We don't do anything for those special // types, so we don't check for that here. - float percent = special.amount + ((l->difficulty == 3) && char_class_is_android(p->disp.visual.char_class) ? 30 : 0); + float percent = special.amount + ((l->difficulty == Difficulty::ULTIMATE) && char_class_is_android(p->disp.visual.char_class) ? 30 : 0); float ep2_factor = (l->episode == Episode::EP2) ? 1.3 : 1.0; - uint32_t stolen_exp = max(min((enemy_exp * percent * ep2_factor) / 100.0f, (l->difficulty + 1) * 20), 1); + uint32_t stolen_exp = max(min((enemy_exp * percent * ep2_factor) / 100.0f, (static_cast(l->difficulty) + 1) * 20), 1); if (c->check_flag(Client::Flag::DEBUG_ENABLED)) { c->log.info_f("Stolen EXP from E-{:03X} with enemy_exp={} percent={:g} stolen_exp={}", ene_st->e_id, enemy_exp, percent, stolen_exp); @@ -4012,17 +4021,20 @@ static asio::awaitable on_enemy_exp_request_bb(shared_ptr c, Subco // relevant players EXP then, if they deserve it). if (!ene_st->ever_hit_by_client_id(c->lobby_client_id) || (ene_st->server_flags & MapState::EnemyState::Flag::EXP_GIVEN)) { + l->log.info_f("EXP already given for this enemy; ignoring request"); co_return; } ene_st->server_flags |= MapState::EnemyState::Flag::EXP_GIVEN; - auto type = ene_st->type(c->version(), l->episode, l->event); + auto type = ene_st->type(c->version(), l->episode, l->difficulty, l->event); double base_exp = base_exp_for_enemy_type( s->battle_params, type, l->episode, l->difficulty, ene_st->super_ene->floor, l->mode == GameMode::SOLO); + l->log.info_f("Base EXP for this enemy ({}) is {:g}", phosg::name_for_enum(type), base_exp); for (size_t client_id = 0; client_id < 4; client_id++) { auto lc = l->clients[client_id]; if (!lc) { + l->log.info_f("No client in slot {}", client_id); continue; } @@ -4038,14 +4050,19 @@ static asio::awaitable on_enemy_exp_request_bb(shared_ptr c, Subco double rate_factor; if (lc->character_file()->disp.stats.level >= 199) { rate_factor = 0.0; + l->log.info_f("Client in slot {} is level 200 and cannot receive EXP", client_id); } else if (ene_st->last_hit_by_client_id(client_id)) { rate_factor = max(1.0, exp_share_multiplier); + l->log.info_f("Client in slot {} killed this enemy; EXP rate is {:g}", client_id, rate_factor); } else if (ene_st->ever_hit_by_client_id(client_id)) { rate_factor = max(0.8, exp_share_multiplier); + l->log.info_f("Client in slot {} tagged this enemy; EXP rate is {:g}", client_id, rate_factor); } else if (lc->floor == ene_st->super_ene->floor) { rate_factor = max(0.0, exp_share_multiplier); + l->log.info_f("Client in slot {} shared this enemy; EXP rate is {:g}", client_id, rate_factor); } else { rate_factor = 0.0; + l->log.info_f("Client in slot {} is not near this enemy; EXP rate is {:g}", client_id, rate_factor); } if (rate_factor > 0.0) { @@ -4061,6 +4078,7 @@ static asio::awaitable on_enemy_exp_request_bb(shared_ptr c, Subco l->base_exp_multiplier * l->challenge_exp_multiplier * (is_ep2 ? 1.3 : 1.0); + l->log.info_f("Client in slot {} receives {} EXP", client_id, player_exp); if (lc->check_flag(Client::Flag::DEBUG_ENABLED)) { send_text_message_fmt(lc, "$C5+{} E-{:03X} {}", player_exp, ene_st->e_id, phosg::name_for_enum(type)); } @@ -5025,9 +5043,9 @@ static asio::awaitable on_quest_F95E_result_bb(shared_ptr c, Subco const auto& cmd = msg.check_size_t(); auto s = c->require_server_state(); - size_t count = (cmd.type > 0x03) ? 1 : (l->difficulty + 1); + size_t count = (cmd.type > 0x03) ? 1 : (static_cast(l->difficulty) + 1); for (size_t z = 0; z < count; z++) { - const auto& results = s->quest_F95E_results.at(cmd.type).at(l->difficulty); + const auto& results = s->quest_F95E_results.at(cmd.type).at(static_cast(l->difficulty)); if (results.empty()) { throw runtime_error("invalid result type"); } @@ -5606,3 +5624,7 @@ asio::awaitable on_subcommand_multi(shared_ptr c, Channel::Message offset += cmd_size; } } + +// TODO; // Dark Falz EXP doesn't work +// TODO; // Guild Card Search doesn't work +// TODO; // Team Search doesn't work diff --git a/src/ReceiveSubcommands.hh b/src/ReceiveSubcommands.hh index c389dd36..61b7c85b 100644 --- a/src/ReceiveSubcommands.hh +++ b/src/ReceiveSubcommands.hh @@ -29,6 +29,7 @@ DropReconcileResult reconcile_drop_request_with_map( std::shared_ptr c, G_SpecializableItemDropRequest_6xA2& cmd, Episode episode, + Difficulty difficulty, uint8_t event, std::shared_ptr map, bool mark_drop); @@ -50,7 +51,7 @@ public: StatusEffectState attack_status_effect; StatusEffectState defense_status_effect; StatusEffectState unused_status_effect; - uint32_t language = 0; + Language language = Language::JAPANESE; uint32_t player_tag = 0; uint32_t guild_card_number = 0; uint32_t unknown_a6 = 0; @@ -79,7 +80,7 @@ public: Parsed6x70Data( const G_SyncPlayerDispAndInventory_DC112000_6x70& cmd, uint32_t guild_card_number, - uint8_t language, + Language language, Version from_version, bool from_client_customization); Parsed6x70Data( @@ -108,7 +109,7 @@ public: G_SyncPlayerDispAndInventory_DC_PC_6x70 as_dc_pc(std::shared_ptr s, Version to_version) const; G_SyncPlayerDispAndInventory_GC_6x70 as_gc_gcnte(std::shared_ptr s, Version to_version) const; G_SyncPlayerDispAndInventory_XB_6x70 as_xb(std::shared_ptr s) const; - G_SyncPlayerDispAndInventory_BB_6x70 as_bb(std::shared_ptr s, uint8_t language) const; + G_SyncPlayerDispAndInventory_BB_6x70 as_bb(std::shared_ptr s, Language language) const; uint64_t default_xb_user_id() const; void clear_v1_unused_item_fields(); diff --git a/src/ReplaySession.cc b/src/ReplaySession.cc index 860fc1bf..fd9675ad 100644 --- a/src/ReplaySession.cc +++ b/src/ReplaySession.cc @@ -50,7 +50,7 @@ ReplaySession::Client::Client(shared_ptr io_context, uint64_t : id(id), port(port), version(version), - channel(make_shared(io_context, this->version, 1, std::format("R-{:X}", this->id))) {} + channel(make_shared(io_context, this->version, Language::ENGLISH, std::format("R-{:X}", this->id))) {} string ReplaySession::Client::str() const { return std::format("Client[{}, T-{}, {}]", this->id, this->port, phosg::name_for_enum(this->version)); diff --git a/src/SaveFileFormats.cc b/src/SaveFileFormats.cc index 2235133c..c4e64a71 100644 --- a/src/SaveFileFormats.cc +++ b/src/SaveFileFormats.cc @@ -15,10 +15,10 @@ struct DefaultSymbolChatEntry { array corner_objects; array face_parts; - SaveFileSymbolChatEntryBB to_entry(uint8_t language) const { + SaveFileSymbolChatEntryBB to_entry(Language language) const { SaveFileSymbolChatEntryBB ret; ret.present = 1; - ret.name.encode(this->language_to_name.at(language), language); + ret.name.encode(this->language_to_name.at(static_cast(language)), language); ret.spec.spec = this->spec; for (size_t z = 0; z < 4; z++) { ret.spec.corner_objects[z] = this->corner_objects[z]; @@ -350,7 +350,7 @@ PlayerDispDataBBPreview PSOBBCharacterFile::to_preview() const { shared_ptr PSOBBCharacterFile::create_from_config( uint32_t guild_card_number, - uint8_t language, + Language language, const PlayerVisualConfig& visual, const std::string& name, shared_ptr level_table) { @@ -540,7 +540,7 @@ shared_ptr PSOBBCharacterFile::create_from_config( shared_ptr PSOBBCharacterFile::create_from_preview( uint32_t guild_card_number, - uint8_t language, + Language language, const PlayerDispDataBBPreview& preview, shared_ptr level_table) { return PSOBBCharacterFile::create_from_config( @@ -550,13 +550,13 @@ shared_ptr PSOBBCharacterFile::create_from_preview( shared_ptr PSOBBCharacterFile::create_from_file(const PSODCNTECharacterFile::Character& src) { auto ret = PSOBBCharacterFile::create_from_config( src.guild_card.guild_card_number, - 0, + Language::JAPANESE, src.disp.visual, - src.disp.visual.name.decode(0), + src.disp.visual.name.decode(Language::JAPANESE), nullptr); ret->inventory = src.inventory; ret->inventory.decode_from_client(Version::DC_V1); - uint8_t language = ret->inventory.language; + Language language = ret->inventory.language; ret->disp = src.disp.to_bb(language, language); ret->validation_flags = 0; ret->creation_timestamp = src.creation_timestamp; @@ -585,11 +585,11 @@ shared_ptr PSOBBCharacterFile::create_from_file(const PSODC1 src.guild_card.guild_card_number, src.inventory.language, src.disp.visual, - src.disp.visual.name.decode(0), + src.disp.visual.name.decode(Language::JAPANESE), nullptr); ret->inventory = src.inventory; ret->inventory.decode_from_client(Version::DC_V1); - uint8_t language = ret->inventory.language; + Language language = ret->inventory.language; ret->disp = src.disp.to_bb(language, language); ret->validation_flags = 0; ret->creation_timestamp = src.creation_timestamp; @@ -628,11 +628,11 @@ shared_ptr PSOBBCharacterFile::create_from_file(const PSODCV src.guild_card.guild_card_number, src.inventory.language, src.disp.visual, - src.disp.visual.name.decode(0), + src.disp.visual.name.decode(Language::JAPANESE), nullptr); ret->inventory = src.inventory; ret->inventory.decode_from_client(Version::DC_V1); - uint8_t language = ret->inventory.language; + Language language = ret->inventory.language; ret->disp = src.disp.to_bb(language, language); ret->validation_flags = src.validation_flags; ret->creation_timestamp = src.creation_timestamp; @@ -661,11 +661,11 @@ shared_ptr PSOBBCharacterFile::create_from_file(const PSODCV src.guild_card.guild_card_number, src.inventory.language, src.disp.visual, - src.disp.visual.name.decode(0), + src.disp.visual.name.decode(Language::JAPANESE), nullptr); ret->inventory = src.inventory; ret->inventory.decode_from_client(Version::DC_V2); - uint8_t language = ret->inventory.language; + Language language = ret->inventory.language; ret->disp = src.disp.to_bb(language, language); ret->validation_flags = src.validation_flags; ret->creation_timestamp = src.creation_timestamp; @@ -701,14 +701,14 @@ shared_ptr PSOBBCharacterFile::create_from_file(const PSOGCN src.guild_card.guild_card_number, src.inventory.language, src.disp.visual, - src.disp.visual.name.decode(0), + src.disp.visual.name.decode(Language::JAPANESE), nullptr); ret->inventory = src.inventory; // Note: We intentionally do not call ret->inventory.decode_from_client here. // This is because the GC client byteswaps data2 in each item before sending // it to the server in the 61 and 98 commands, but GetExtendedPlayerInfo does // not do this, so the data2 fields are already in the correct order here. - uint8_t language = ret->inventory.language; + Language language = ret->inventory.language; ret->disp = src.disp.to_bb(language, language); ret->validation_flags = src.validation_flags; ret->creation_timestamp = src.creation_timestamp; @@ -742,14 +742,14 @@ shared_ptr PSOBBCharacterFile::create_from_file(const PSOGCC src.guild_card.guild_card_number, src.inventory.language, src.disp.visual, - src.disp.visual.name.decode(0), + src.disp.visual.name.decode(Language::JAPANESE), nullptr); ret->inventory = src.inventory; // Note: We intentionally do not call ret->inventory.decode_from_client here. // This is because the GC client byteswaps data2 in each item before sending // it to the server in the 61 and 98 commands, but GetExtendedPlayerInfo does // not do this, so the data2 fields are already in the correct order here. - uint8_t language = ret->inventory.language; + Language language = ret->inventory.language; ret->disp = src.disp.to_bb(language, language); ret->validation_flags = src.validation_flags; ret->creation_timestamp = src.creation_timestamp; @@ -793,10 +793,10 @@ shared_ptr PSOBBCharacterFile::create_from_file(const PSOGCE src.guild_card.guild_card_number, src.inventory.language, src.disp.visual, - src.disp.visual.name.decode(0), + src.disp.visual.name.decode(Language::JAPANESE), nullptr); ret->inventory = src.inventory; - uint8_t language = ret->inventory.language; + Language language = ret->inventory.language; ret->disp = src.disp.to_bb(language, language); ret->validation_flags = src.validation_flags; ret->creation_timestamp = src.creation_timestamp; @@ -841,11 +841,11 @@ shared_ptr PSOBBCharacterFile::create_from_file(const PSOXBC src.guild_card.guild_card_number, src.inventory.language, src.disp.visual, - src.disp.visual.name.decode(0), + src.disp.visual.name.decode(Language::JAPANESE), nullptr); ret->inventory = src.inventory; ret->inventory.decode_from_client(Version::XB_V3); - uint8_t language = ret->inventory.language; + Language language = ret->inventory.language; ret->disp = src.disp.to_bb(language, language); ret->validation_flags = src.validation_flags; ret->creation_timestamp = src.creation_timestamp; @@ -886,7 +886,7 @@ shared_ptr PSOBBCharacterFile::create_from_file(const PSOXBC } PSODCNTECharacterFile::Character PSOBBCharacterFile::as_dc_nte(uint64_t hardware_id) const { - uint8_t language = this->inventory.language; + Language language = this->inventory.language; PSODCNTECharacterFile::Character ret; ret.inventory = this->inventory; @@ -912,7 +912,7 @@ PSODCNTECharacterFile::Character PSOBBCharacterFile::as_dc_nte(uint64_t hardware } PSODC112000CharacterFile::Character PSOBBCharacterFile::as_11_2000(uint64_t hardware_id) const { - uint8_t language = this->inventory.language; + Language language = this->inventory.language; PSODC112000CharacterFile::Character ret; ret.inventory = this->inventory; @@ -949,7 +949,7 @@ PSODC112000CharacterFile::Character PSOBBCharacterFile::as_11_2000(uint64_t hard } PSOBBCharacterFile::operator PSODCV1CharacterFile::Character() const { - uint8_t language = this->inventory.language; + Language language = this->inventory.language; PSODCV1CharacterFile::Character ret; ret.inventory = this->inventory; @@ -981,7 +981,7 @@ PSOBBCharacterFile::operator PSODCV1CharacterFile::Character() const { } PSOBBCharacterFile::operator PSODCV2CharacterFile::Character() const { - uint8_t language = this->inventory.language; + Language language = this->inventory.language; PSODCV2CharacterFile::Character ret; ret.inventory = this->inventory; @@ -1022,7 +1022,7 @@ PSOBBCharacterFile::operator PSODCV2CharacterFile::Character() const { } PSOBBCharacterFile::operator PSOGCNTECharacterFileCharacter() const { - uint8_t language = this->inventory.language; + Language language = this->inventory.language; PSOGCNTECharacterFileCharacter ret; ret.inventory = this->inventory; @@ -1060,7 +1060,7 @@ PSOBBCharacterFile::operator PSOGCNTECharacterFileCharacter() const { } PSOBBCharacterFile::operator PSOGCCharacterFile::Character() const { - uint8_t language = this->inventory.language; + Language language = this->inventory.language; PSOGCCharacterFile::Character ret; ret.inventory = this->inventory; @@ -1108,7 +1108,7 @@ PSOBBCharacterFile::operator PSOGCCharacterFile::Character() const { } PSOBBCharacterFile::operator PSOXBCharacterFile::Character() const { - uint8_t language = this->inventory.language; + Language language = this->inventory.language; PSOXBCharacterFile::Character ret; ret.inventory = this->inventory; diff --git a/src/SaveFileFormats.hh b/src/SaveFileFormats.hh index 02cc134d..6968ed1d 100644 --- a/src/SaveFileFormats.hh +++ b/src/SaveFileFormats.hh @@ -193,7 +193,7 @@ struct SaveFileChatShortcutEntryT { /* 40:54:A4 */ template - SaveFileChatShortcutEntryT convert(uint8_t language) const { + SaveFileChatShortcutEntryT convert(Language language) const { SaveFileChatShortcutEntryT ret; ret.type = this->type; switch (ret.type) { @@ -268,7 +268,7 @@ struct PSOPCSystemFile { // PSO______COM // assumption that Sega didn't change much between versions. /* 0004 */ le_int16_t music_volume = 0; /* 0006 */ int8_t sound_volume = 0; - /* 0007 */ uint8_t language = 1; + /* 0007 */ Language language = Language::ENGLISH; /* 0008 */ le_int32_t server_time_delta_frames = 1728000; /* 000C */ parray unknown_a4; // Last one is always 0x1234? /* 002C */ parray event_flags; @@ -281,7 +281,7 @@ struct PSOGCSystemFile { /* 0000 */ be_uint32_t checksum = 0; /* 0004 */ be_int16_t music_volume = 0; // 0 = full volume; -250 = min volume /* 0006 */ int8_t sound_volume = 0; // 0 = full volume; -100 = min volume - /* 0007 */ uint8_t language = 1; + /* 0007 */ Language language = Language::ENGLISH; // This field stores the effective time zone offset between the server and // client, in frames. The default value is 1728000, which corresponds to 16 // hours. This is recomputed when the client receives a B1 command. @@ -309,7 +309,7 @@ struct PSOXBSystemFile { /* 0000 */ le_uint32_t checksum = 0; /* 0004 */ le_int16_t music_volume = -50; /* 0006 */ int8_t sound_volume = 0; - /* 0007 */ uint8_t language = 0; + /* 0007 */ Language language = Language::JAPANESE; /* 0008 */ be_int32_t server_time_delta_frames = 200; /* 000C */ be_uint16_t udp_behavior = 0; // 0 = auto, 1 = on, 2 = off /* 000E */ be_uint16_t surround_sound_enabled = 0; @@ -338,7 +338,7 @@ struct PSOBBMinimalSystemFile { /* 0000 */ be_uint32_t checksum = 0; /* 0004 */ be_int16_t music_volume = 0; /* 0006 */ int8_t sound_volume = 0; - /* 0007 */ uint8_t language = 0; + /* 0007 */ Language language = Language::JAPANESE; /* 0008 */ be_int32_t server_time_delta_frames = 1728000; /* 000C */ be_uint16_t udp_behavior = 0; // 0 = auto, 1 = on, 2 = off /* 000E */ be_uint16_t surround_sound_enabled = 0; @@ -871,13 +871,13 @@ struct PSOBBCharacterFile { static std::shared_ptr create_from_config( uint32_t guild_card_number, - uint8_t language, + Language language, const PlayerVisualConfig& visual, const std::string& name, std::shared_ptr level_table); static std::shared_ptr create_from_preview( uint32_t guild_card_number, - uint8_t language, + Language language, const PlayerDispDataBBPreview& preview, std::shared_ptr level_table); static std::shared_ptr create_from_file(const PSODCNTECharacterFile::Character& src); @@ -978,7 +978,7 @@ struct PSODCV1V2GuildCardFile { /* 0004 */ parray entries; /* 3204 */ le_int16_t music_volume = 0; /* 3206 */ int8_t sound_volume = 0; - /* 3207 */ uint8_t language = 1; + /* 3207 */ Language language = Language::ENGLISH; /* 3208 */ le_int32_t server_time_delta_frames = 540000; // 648000 on DCv1 /* 320C */ le_uint32_t creation_timestamp = 0; /* 3210 */ le_uint32_t round2_seed = 0; diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 1fa6450f..1f616229 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -306,7 +306,7 @@ void send_set_guild_card_number(shared_ptr c) { } void send_patch_enter_directory(shared_ptr c, const string& dir) { - S_EnterDirectory_Patch_09 cmd = {{dir, 1}}; + S_EnterDirectory_Patch_09 cmd = {{dir, Language::ENGLISH}}; c->channel->send(0x09, 0x00, cmd); } @@ -793,9 +793,9 @@ void send_complete_player_bb(shared_ptr c) { auto sys = c->system_file(true); auto team = c->team(); if (c->check_flag(Client::Flag::FORCE_ENGLISH_LANGUAGE_BB)) { - p->inventory.language = 1; - p->guild_card.language = 1; - sys->language = 1; + p->inventory.language = Language::ENGLISH; + p->guild_card.language = Language::ENGLISH; + sys->language = Language::ENGLISH; } SC_SyncSaveFiles_BB_E7 cmd; @@ -1016,7 +1016,7 @@ void send_text_or_scrolling_message(shared_ptr s, const std::string string prepare_chat_data( Version version, - uint8_t language, + Language language, uint8_t from_client_id, const string& from_name, const string& text, @@ -1024,7 +1024,7 @@ string prepare_chat_data( string data; if (version == Version::BB_V4) { - data.append(language ? "\tE" : "\tJ"); + data.append((language == Language::JAPANESE) ? "\tJ" : "\tE"); } data.append(from_name); if (version == Version::DC_NTE) { @@ -1037,7 +1037,7 @@ string prepare_chat_data( } if (uses_utf16(version)) { - data.append(language ? "\tE" : "\tJ"); + data.append((language == Language::JAPANESE) ? "\tJ" : "\tE"); data.append(text); return tt_utf8_to_utf16(data); } else if (version == Version::DC_NTE) { @@ -1291,7 +1291,7 @@ void send_guild_card_dc_pc_gc_t( uint32_t guild_card_number, const string& name, const string& description, - uint8_t language, + Language language, uint8_t section_id, uint8_t char_class) { CmdT cmd; @@ -1315,7 +1315,7 @@ void send_guild_card_xb( uint64_t xb_user_id, const string& name, const string& description, - uint8_t language, + Language language, uint8_t section_id, uint8_t char_class) { G_SendGuildCard_XB_6x06 cmd; @@ -1341,7 +1341,7 @@ static void send_guild_card_bb( const string& name, const string& team_name, const string& description, - uint8_t language, + Language language, uint8_t section_id, uint8_t char_class) { G_SendGuildCard_BB_6x06 cmd; @@ -1366,7 +1366,7 @@ void send_guild_card( const string& name, const string& team_name, const string& description, - uint8_t language, + Language language, uint8_t section_id, uint8_t char_class) { switch (ch->version) { @@ -1573,7 +1573,7 @@ void send_game_menu_t( auto& e = entries.emplace_back(); e.menu_id = MenuID::GAME; e.item_id = l->lobby_id; - e.difficulty_tag = (is_ep3(c->version()) ? 0x0A : (l->difficulty + 0x22)); + e.difficulty_tag = (is_ep3(c->version()) ? 0x0A : (static_cast(l->difficulty) + 0x22)); e.num_players = l->count_clients(); if (is_dc(c->version())) { e.episode = l->version_is_allowed(Version::DC_V1) ? 1 : 0; @@ -2952,12 +2952,12 @@ void send_game_flag_state_t(shared_ptr c) { if (l->quest_flags_known) { // Not all flags known; send multiple 6x75s phosg::StringWriter w; bool use_v3_cmd = !is_v1_or_v2(c->version()) || (c->version() == Version::GC_NTE); - for (uint8_t difficulty = 0; difficulty < 4; difficulty++) { + for (Difficulty difficulty : ALL_DIFFICULTIES_V234) { if ((difficulty != l->difficulty) && !use_v3_cmd) { continue; } - const auto& diff_flags = l->quest_flag_values->data.at(difficulty); - const auto& diff_known_flags = l->quest_flags_known->data.at(difficulty); + const auto& diff_flags = l->quest_flag_values->for_difficulty(difficulty); + const auto& diff_known_flags = l->quest_flags_known->for_difficulty(difficulty); for (uint8_t z = 0; z < diff_known_flags.data.size(); z++) { uint8_t known_flags = diff_known_flags.data[z]; if (!known_flags) { @@ -2969,7 +2969,7 @@ void send_game_flag_state_t(shared_ptr c) { uint16_t flag_num = ((z << 3) | sh); if (use_v3_cmd) { w.put(G_UpdateQuestFlag_V3_BB_6x75{ - {{0x75, 0x03, 0x0000}, flag_num, (((flag_values << sh) & 0x80) ? 0 : 1)}, difficulty, 0}); + {{0x75, 0x03, 0x0000}, flag_num, (((flag_values << sh) & 0x80) ? 0 : 1)}, static_cast(difficulty), 0}); } else { w.put(G_UpdateQuestFlag_DC_PC_6x75{ {0x75, 0x02, 0x0000}, flag_num, (((flag_values << sh) & 0x80) ? 0 : 1)}); @@ -3541,7 +3541,7 @@ string ep3_description_for_client(shared_ptr c) { "{} CLv{} {}", name_for_char_class(p->disp.visual.char_class), p->disp.stats.level + 1, - char_for_language_code(p->inventory.language)); + char_for_language(p->inventory.language)); } template @@ -3783,7 +3783,7 @@ void send_ep3_tournament_match_result(shared_ptr l, uint32_t meseta_rewar cmd.num_players_per_team = match->preceding_a->winner_team->max_players; cmd.winner_team_id = (match->preceding_b->winner_team == match->winner_team); cmd.meseta_amount = meseta_reward; - cmd.meseta_reward_text.encode("You got %s meseta!", 1); + cmd.meseta_reward_text.encode("You got %s meseta!", Language::ENGLISH); if ((lc->version() != Version::GC_EP3_NTE) && !(s->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) { uint8_t mask_key = (phosg::random_object() % 0xFF) + 1; @@ -3854,7 +3854,7 @@ void send_ep3_update_game_metadata(shared_ptr l) { } cmd.total_spectators = total_spectators; cmd.text_size = text.size(); - cmd.text.encode(text, 1); + cmd.text.encode(text, Language::ENGLISH); for (auto c : watcher_l->clients) { if (c) { if ((c->version() == Version::GC_EP3) && !(s->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) { diff --git a/src/SendCommands.hh b/src/SendCommands.hh index eaf7bc1b..bad8230f 100644 --- a/src/SendCommands.hh +++ b/src/SendCommands.hh @@ -231,7 +231,7 @@ void send_text_or_scrolling_message(std::shared_ptr s, const std::s std::string prepare_chat_data( Version version, - uint8_t language, + Language language, uint8_t from_client_id, const std::string& from_name, const std::string& text, @@ -298,7 +298,7 @@ void send_guild_card( const std::string& name, const std::string& team_name, const std::string& description, - uint8_t language, + Language language, uint8_t section_id, uint8_t char_class); void send_guild_card(std::shared_ptr c, std::shared_ptr source); diff --git a/src/ServerState.cc b/src/ServerState.cc index 71c80f3c..afcac4b0 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -394,7 +394,7 @@ shared_ptr> ServerState::information_contents_for_client(sh return is_v1_or_v2(c->version()) ? this->information_contents_v2 : this->information_contents_v3; } -size_t ServerState::default_min_level_for_game(Version version, Episode episode, uint8_t difficulty) const { +size_t ServerState::default_min_level_for_game(Version version, Episode episode, Difficulty difficulty) const { const auto& min_levels = is_v4(version) ? this->min_levels_v4 : is_v3(version) @@ -402,21 +402,21 @@ size_t ServerState::default_min_level_for_game(Version version, Episode episode, : this->min_levels_v1_v2; switch (episode) { case Episode::EP1: - return min_levels[0].at(difficulty); + return min_levels[0].at(static_cast(difficulty)); case Episode::EP2: - return min_levels[1].at(difficulty); + return min_levels[1].at(static_cast(difficulty)); case Episode::EP3: return 0; case Episode::EP4: - return min_levels[2].at(difficulty); + return min_levels[2].at(static_cast(difficulty)); default: throw runtime_error("invalid episode"); } } shared_ptr ServerState::set_data_table( - Version version, Episode episode, GameMode mode, uint8_t difficulty) const { - bool use_ult_tables = ((episode == Episode::EP1) && (difficulty == 3) && !is_v1(version) && (version != Version::PC_NTE)); + Version version, Episode episode, GameMode mode, Difficulty difficulty) const { + bool use_ult_tables = ((episode == Episode::EP1) && (difficulty == Difficulty::ULTIMATE) && !is_v1(version) && (version != Version::PC_NTE)); if (mode == GameMode::SOLO && is_v4(version)) { return use_ult_tables ? this->bb_solo_set_data_table_ep1_ult : this->bb_solo_set_data_table; } @@ -1280,15 +1280,16 @@ void ServerState::load_config_early() { } catch (const out_of_range&) { } - for (size_t z = 0; z < 4; z++) { + for (Difficulty difficulty : ALL_DIFFICULTIES_V234) { + size_t diff_index = static_cast(difficulty); shared_ptr prev = MapState::DEFAULT_RARE_ENEMIES; try { string key = "RareEnemyRates-"; - key += token_name_for_difficulty(z); - this->rare_enemy_rates_by_difficulty[z] = make_shared(this->config_json->at(key)); - prev = this->rare_enemy_rates_by_difficulty[z]; + key += token_name_for_difficulty(difficulty); + this->rare_enemy_rates_by_difficulty[diff_index] = make_shared(this->config_json->at(key)); + prev = this->rare_enemy_rates_by_difficulty[diff_index]; } catch (const out_of_range&) { - this->rare_enemy_rates_by_difficulty[z] = prev; + this->rare_enemy_rates_by_difficulty[diff_index] = prev; } } try { @@ -1599,7 +1600,7 @@ void ServerState::load_maps() { auto objects_data = this->load_map_file(Version::GC_EP3, "map_city_on_battle_o.dat"); auto enemies_data = this->load_map_file(Version::GC_EP3, "map_city_on_battle_e.dat"); if (objects_data || enemies_data) { - uint32_t free_play_key = this->free_play_key(Episode::EP3, GameMode::NORMAL, 0, 0, 0, 0); + uint32_t free_play_key = this->free_play_key(Episode::EP3, GameMode::NORMAL, Difficulty::NORMAL, 0, 0, 0); auto map_file = make_shared(0, objects_data, enemies_data, nullptr); new_map_file_for_source_hash.emplace(map_file->source_hash(), map_file); new_map_files_for_free_play_key[free_play_key].at(static_cast(Version::GC_EP3)) = map_file; @@ -1611,15 +1612,13 @@ void ServerState::load_maps() { config_log.info_f("Loading free play map files"); for (Version v : ALL_ARPG_SEMANTIC_VERSIONS) { - const array episodes = {Episode::EP1, Episode::EP2, Episode::EP4}; - for (Episode episode : episodes) { + for (Episode episode : ALL_EPISODES_V4) { if ((episode == Episode::EP2 && is_v1_or_v2(v) && (v != Version::GC_NTE)) || (episode == Episode::EP4 && !is_v4(v))) { continue; } - const array modes = {GameMode::NORMAL, GameMode::BATTLE, GameMode::CHALLENGE, GameMode::SOLO}; - for (GameMode mode : modes) { + for (GameMode mode : ALL_GAME_MODES_V4) { if ((mode == GameMode::BATTLE) && is_pre_v1(v)) { continue; } @@ -1629,8 +1628,8 @@ void ServerState::load_maps() { if ((mode == GameMode::SOLO && !is_v4(v))) { continue; } - for (uint8_t difficulty = 0; difficulty < 4; difficulty++) { - if ((difficulty == 3) && is_v1(v)) { + for (Difficulty difficulty : ALL_DIFFICULTIES_V234) { + if ((difficulty == Difficulty::ULTIMATE) && is_v1(v)) { continue; } auto sdt = this->set_data_table(v, episode, mode, difficulty); @@ -1705,7 +1704,7 @@ void ServerState::load_maps() { } shared_ptr ServerState::get_free_play_supermap( - Episode episode, GameMode mode, uint8_t difficulty, uint8_t floor, uint32_t layout, uint32_t entities) { + Episode episode, GameMode mode, Difficulty difficulty, uint8_t floor, uint32_t layout, uint32_t entities) { uint32_t free_play_key = this->free_play_key(episode, mode, difficulty, floor, layout, entities); try { return this->supermap_for_free_play_key.at(free_play_key); @@ -1759,10 +1758,7 @@ shared_ptr ServerState::get_free_play_supermap( } vector> ServerState::supermaps_for_variations( - Episode episode, - GameMode mode, - uint8_t difficulty, - const Variations& variations) { + Episode episode, GameMode mode, Difficulty difficulty, const Variations& variations) { vector> ret; for (size_t floor = 0; floor < 0x12; floor++) { Variations::Entry e; @@ -1881,7 +1877,7 @@ void ServerState::load_word_select_table() { unique_ptr pc_unitxt_data; if (this->text_index) { config_log.debug_f("(Word select) Using PC_V2 unitxt_e.prs from text index"); - pc_unitxt_collection = &this->text_index->get(Version::PC_V2, 1, 35); + pc_unitxt_collection = &this->text_index->get(Version::PC_V2, Language::ENGLISH, 35); } else { config_log.debug_f("(Word select) Loading PC_V2 unitxt_e.prs"); pc_unitxt_data = make_unique(phosg::load_file("system/text-sets/pc-v2/unitxt_e.prs")); @@ -1930,25 +1926,25 @@ shared_ptr ServerState::create_item_name_index_for_version( shared_ptr text_index) const { switch (limits->version) { case Version::DC_NTE: - return make_shared(pmt, limits, text_index->get(Version::DC_NTE, 0, 2)); + return make_shared(pmt, limits, text_index->get(Version::DC_NTE, Language::JAPANESE, 2)); case Version::DC_11_2000: - return make_shared(pmt, limits, text_index->get(Version::DC_11_2000, 1, 2)); + return make_shared(pmt, limits, text_index->get(Version::DC_11_2000, Language::ENGLISH, 2)); case Version::DC_V1: - return make_shared(pmt, limits, text_index->get(Version::DC_V1, 1, 2)); + return make_shared(pmt, limits, text_index->get(Version::DC_V1, Language::ENGLISH, 2)); case Version::DC_V2: - return make_shared(pmt, limits, text_index->get(Version::DC_V2, 1, 3)); + return make_shared(pmt, limits, text_index->get(Version::DC_V2, Language::ENGLISH, 3)); case Version::PC_NTE: - return make_shared(pmt, limits, text_index->get(Version::PC_NTE, 1, 3)); + return make_shared(pmt, limits, text_index->get(Version::PC_NTE, Language::ENGLISH, 3)); case Version::PC_V2: - return make_shared(pmt, limits, text_index->get(Version::PC_V2, 1, 3)); + return make_shared(pmt, limits, text_index->get(Version::PC_V2, Language::ENGLISH, 3)); case Version::GC_NTE: - return make_shared(pmt, limits, text_index->get(Version::GC_NTE, 1, 0)); + return make_shared(pmt, limits, text_index->get(Version::GC_NTE, Language::ENGLISH, 0)); case Version::GC_V3: - return make_shared(pmt, limits, text_index->get(Version::GC_V3, 1, 0)); + return make_shared(pmt, limits, text_index->get(Version::GC_V3, Language::ENGLISH, 0)); case Version::XB_V3: - return make_shared(pmt, limits, text_index->get(Version::XB_V3, 1, 0)); + return make_shared(pmt, limits, text_index->get(Version::XB_V3, Language::ENGLISH, 0)); case Version::BB_V4: - return make_shared(pmt, limits, text_index->get(Version::BB_V4, 1, 1)); + return make_shared(pmt, limits, text_index->get(Version::BB_V4, Language::ENGLISH, 1)); default: return nullptr; } diff --git a/src/ServerState.hh b/src/ServerState.hh index 029db8f8..4106af63 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -199,7 +199,7 @@ struct ServerState : public std::enable_shared_from_this { std::unordered_map> rare_item_sets; std::shared_ptr armor_random_set; std::shared_ptr tool_random_set; - std::array, 4> weapon_random_sets; + std::array, 4> weapon_random_sets; // Keyed oin difficulty std::shared_ptr tekker_adjustment_set; std::array, NUM_VERSIONS> item_parameter_tables; std::shared_ptr item_translation_table; @@ -344,7 +344,14 @@ struct ServerState : public std::enable_shared_from_this { std::shared_ptr proxy_destinations_menu(Version version) const; const std::vector>& proxy_destinations(Version version) const; - std::shared_ptr set_data_table(Version version, Episode episode, GameMode mode, uint8_t difficulty) const; + std::shared_ptr set_data_table(Version version, Episode episode, GameMode mode, Difficulty difficulty) const; + + inline std::shared_ptr weapon_random_set(Difficulty difficulty) const { + return this->weapon_random_sets.at(static_cast(difficulty)); + } + inline std::shared_ptr rare_enemy_rates(Difficulty difficulty) const { + return this->rare_enemy_rates_by_difficulty.at(static_cast(difficulty)); + } std::shared_ptr level_table(Version version) const; std::shared_ptr item_parameter_table(Version version) const; @@ -376,7 +383,7 @@ struct ServerState : public std::enable_shared_from_this { std::shared_ptr> information_contents_for_client(std::shared_ptr c) const; - size_t default_min_level_for_game(Version version, Episode episode, uint8_t difficulty) const; + size_t default_min_level_for_game(Version version, Episode episode, Difficulty difficulty) const; void set_port_configuration(const std::vector& port_configs); @@ -391,7 +398,7 @@ struct ServerState : public std::enable_shared_from_this { std::vector parse_port_configuration(const phosg::JSON& json) const; static constexpr uint32_t free_play_key( - Episode episode, GameMode mode, uint8_t difficulty, uint8_t floor, uint32_t layout, uint32_t entities) { + Episode episode, GameMode mode, Difficulty difficulty, uint8_t floor, uint32_t layout, uint32_t entities) { return (static_cast(episode) << 28) | (static_cast(mode) << 26) | (static_cast(difficulty) << 24) | @@ -400,11 +407,11 @@ struct ServerState : public std::enable_shared_from_this { (static_cast(entities) << 0); } std::shared_ptr get_free_play_supermap( - Episode episode, GameMode mode, uint8_t difficulty, uint8_t floor, uint32_t layout, uint32_t entities); + Episode episode, GameMode mode, Difficulty difficulty, uint8_t floor, uint32_t layout, uint32_t entities); std::vector> supermaps_for_variations( Episode episode, GameMode mode, - uint8_t difficulty, + Difficulty difficulty, const Variations& variations); void create_default_lobbies(); diff --git a/src/ShellCommands.cc b/src/ShellCommands.cc index b9f8c835..d3a89e74 100644 --- a/src/ShellCommands.cc +++ b/src/ShellCommands.cc @@ -940,7 +940,7 @@ ShellCommand c_show_slots( if (player.guild_card_number) { ret.emplace_back(format(" {}: {} => {} ({}, {}, {})", z, player.guild_card_number, player.name, - char_for_language_code(player.language), + char_for_language(player.language), name_for_char_class(player.char_class), name_for_section_id(player.section_id))); } else { diff --git a/src/StaticGameData.cc b/src/StaticGameData.cc index b4ec3f15..964fe130 100644 --- a/src/StaticGameData.cc +++ b/src/StaticGameData.cc @@ -476,36 +476,36 @@ bool char_class_is_force(uint8_t cls) { return class_flags.at(cls) & ClassFlag::FORCE; } -const char* name_for_difficulty(uint8_t difficulty) { +const char* name_for_difficulty(Difficulty difficulty) { static const array names = { "Normal", "Hard", "Very Hard", "Ultimate"}; try { - return names.at(difficulty); + return names.at(static_cast(difficulty)); } catch (const out_of_range&) { return "Unknown"; } } -const char* token_name_for_difficulty(uint8_t difficulty) { +const char* token_name_for_difficulty(Difficulty difficulty) { static const array names = { "Normal", "Hard", "VeryHard", "Ultimate"}; try { - return names.at(difficulty); + return names.at(static_cast(difficulty)); } catch (const out_of_range&) { return "Unknown"; } } -char abbreviation_for_difficulty(uint8_t difficulty) { +char abbreviation_for_difficulty(Difficulty difficulty) { static const array names = {'N', 'H', 'V', 'U'}; try { - return names.at(difficulty); + return names.at(static_cast(difficulty)); } catch (const out_of_range&) { return '?'; } } -const char* name_for_language_code(uint8_t language_code) { +const char* name_for_language(Language language) { array names = {{"Japanese", "English", "German", @@ -514,39 +514,41 @@ const char* name_for_language_code(uint8_t language_code) { "Simplified Chinese", "Traditional Chinese", "Korean"}}; - return (language_code < 8) ? names[language_code] : "Unknown"; + size_t lang_index = static_cast(language); + return (lang_index < 8) ? names[lang_index] : "Unknown"; } -char char_for_language_code(uint8_t language_code) { - return (language_code < 8) ? "JEGFSBTK"[language_code] : '?'; +char char_for_language(Language language) { + size_t lang_index = static_cast(language); + return (lang_index < 8) ? "JEGFSBTK"[lang_index] : '?'; } -uint8_t language_code_for_char(char language_char) { +Language language_for_char(char language_char) { switch (language_char) { case 'J': case 'j': - return 0; + return Language::JAPANESE; case 'E': case 'e': - return 1; + return Language::ENGLISH; case 'G': case 'g': - return 2; + return Language::GERMAN; case 'F': case 'f': - return 3; + return Language::FRENCH; case 'S': case 's': - return 4; + return Language::SPANISH; case 'B': case 'b': - return 5; + return Language::SIMPLIFIED_CHINESE; case 'T': case 't': - return 6; + return Language::TRADITIONAL_CHINESE; case 'K': case 'k': - return 7; + return Language::KOREAN; default: throw runtime_error("unknown language"); } @@ -809,3 +811,12 @@ const array DEFAULT_MIN_LEVELS_V123({0, 19, 39, 79}); const array DEFAULT_MIN_LEVELS_V4_EP1({0, 19, 39, 79}); const array DEFAULT_MIN_LEVELS_V4_EP2({0, 29, 49, 89}); const array DEFAULT_MIN_LEVELS_V4_EP4({0, 39, 79, 109}); + +const array ALL_GAME_MODES_V1 = {GameMode::NORMAL, GameMode::BATTLE}; +const array ALL_GAME_MODES_V23 = {GameMode::NORMAL, GameMode::BATTLE, GameMode::CHALLENGE}; +const array ALL_GAME_MODES_V4 = {GameMode::NORMAL, GameMode::BATTLE, GameMode::CHALLENGE, GameMode::SOLO}; +const array ALL_EPISODES_V12 = {Episode::EP1}; +const array ALL_EPISODES_V3 = {Episode::EP1, Episode::EP2}; +const array ALL_EPISODES_V4 = {Episode::EP1, Episode::EP2, Episode::EP4}; +const array ALL_DIFFICULTIES_V1 = {Difficulty::NORMAL, Difficulty::HARD, Difficulty::VERY_HARD}; +const array ALL_DIFFICULTIES_V234 = {Difficulty::NORMAL, Difficulty::HARD, Difficulty::VERY_HARD, Difficulty::ULTIMATE}; diff --git a/src/StaticGameData.hh b/src/StaticGameData.hh index df622882..cd88d8f9 100644 --- a/src/StaticGameData.hh +++ b/src/StaticGameData.hh @@ -17,6 +17,26 @@ enum class Episode { EP4 = 4, }; +enum class Difficulty : uint8_t { + NORMAL = 0, + HARD = 1, + VERY_HARD = 2, + ULTIMATE = 3, + UNKNOWN = 0xFF, +}; + +enum class Language : uint8_t { + JAPANESE = 0, + ENGLISH = 1, + GERMAN = 2, + FRENCH = 3, + SPANISH = 4, + SIMPLIFIED_CHINESE = 5, + TRADITIONAL_CHINESE = 6, + KOREAN = 7, + UNKNOWN = 0xFF, +}; + bool episode_has_arpg_semantics(Episode ep); const char* name_for_episode(Episode ep); const char* token_name_for_episode(Episode ep); @@ -63,13 +83,13 @@ bool char_class_is_hunter(uint8_t cls); bool char_class_is_ranger(uint8_t cls); bool char_class_is_force(uint8_t cls); -const char* name_for_difficulty(uint8_t difficulty); -const char* token_name_for_difficulty(uint8_t difficulty); -char abbreviation_for_difficulty(uint8_t difficulty); +const char* name_for_difficulty(Difficulty difficulty); +const char* token_name_for_difficulty(Difficulty difficulty); +char abbreviation_for_difficulty(Difficulty difficulty); -const char* name_for_language_code(uint8_t language_code); -char char_for_language_code(uint8_t language_code); -uint8_t language_code_for_char(char language_char); +const char* name_for_language(Language language); +char char_for_language(Language language); +Language language_for_char(char language_char); extern const std::vector name_for_mag_color; extern const std::unordered_map mag_color_for_name; @@ -110,3 +130,12 @@ extern const std::array DEFAULT_MIN_LEVELS_V123; extern const std::array DEFAULT_MIN_LEVELS_V4_EP1; extern const std::array DEFAULT_MIN_LEVELS_V4_EP2; extern const std::array DEFAULT_MIN_LEVELS_V4_EP4; + +extern const std::array ALL_GAME_MODES_V1; +extern const std::array ALL_GAME_MODES_V23; +extern const std::array ALL_GAME_MODES_V4; +extern const std::array ALL_EPISODES_V12; +extern const std::array ALL_EPISODES_V3; +extern const std::array ALL_EPISODES_V4; +extern const std::array ALL_DIFFICULTIES_V1; +extern const std::array ALL_DIFFICULTIES_V234; diff --git a/src/Text.cc b/src/Text.cc index 5cdac643..b3b3aa50 100644 --- a/src/Text.cc +++ b/src/Text.cc @@ -275,50 +275,50 @@ TextTranscoder tt_utf8_to_utf16("UTF-16LE", "UTF-8"); TextTranscoder tt_ascii_to_utf8("UTF-8", "ASCII"); TextTranscoder tt_utf8_to_ascii("ASCII", "UTF-8"); -string tt_encode_marked_optional(const string& utf8, uint8_t default_language, bool is_utf16) { +string tt_encode_marked_optional(const string& utf8, Language default_language, bool is_utf16) { if (is_utf16) { return tt_utf8_to_utf16(utf8); } else { - if (default_language) { - try { - return tt_utf8_to_8859(utf8); - } catch (const exception& e) { - return "\tJ" + tt_utf8_to_sega_sjis(utf8); - } - } else { + if (default_language == Language::JAPANESE) { try { return tt_utf8_to_sega_sjis(utf8); } catch (const exception& e) { return "\tE" + tt_utf8_to_8859(utf8); } + } else { + try { + return tt_utf8_to_8859(utf8); + } catch (const exception& e) { + return "\tJ" + tt_utf8_to_sega_sjis(utf8); + } } } } -string tt_encode_marked(const string& utf8, uint8_t default_language, bool is_utf16) { +string tt_encode_marked(const string& utf8, Language default_language, bool is_utf16) { if (is_utf16) { string to_encode = "\t"; - to_encode += marker_for_language_code(default_language); + to_encode += marker_for_language(default_language); to_encode += utf8; return tt_utf8_to_utf16(to_encode); } else { - if (default_language) { + if (default_language == Language::JAPANESE) { try { - return "\tE" + tt_utf8_to_8859(utf8); - } catch (const exception& e) { return "\tJ" + tt_utf8_to_sega_sjis(utf8); + } catch (const exception& e) { + return "\tE" + tt_utf8_to_8859(utf8); } } else { try { - return "\tJ" + tt_utf8_to_sega_sjis(utf8); - } catch (const exception& e) { return "\tE" + tt_utf8_to_8859(utf8); + } catch (const exception& e) { + return "\tJ" + tt_utf8_to_sega_sjis(utf8); } } } } -string tt_decode_marked(const string& data, uint8_t default_language, bool is_utf16) { +string tt_decode_marked(const string& data, Language default_language, bool is_utf16) { if (is_utf16) { string ret = tt_utf16_to_utf8(data); if (ret.size() >= 2 && ret[0] == '\t' && is_language_marker_utf16(ret[1])) { @@ -333,7 +333,7 @@ string tt_decode_marked(const string& data, uint8_t default_language, bool is_ut return tt_8859_to_utf8(data.substr(2)); } } - return default_language ? tt_8859_to_utf8(data) : tt_sega_sjis_to_utf8(data); + return (default_language == Language::JAPANESE) ? tt_sega_sjis_to_utf8(data) : tt_8859_to_utf8(data); } } @@ -486,20 +486,20 @@ string escape_player_name(const string& name) { } } -char marker_for_language_code(uint8_t language_code) { - switch (language_code) { - case 0: +char marker_for_language(Language language) { + switch (language) { + case Language::JAPANESE: return 'J'; - case 1: - case 2: - case 3: - case 4: + case Language::ENGLISH: + case Language::GERMAN: + case Language::FRENCH: + case Language::SPANISH: return 'E'; - case 5: + case Language::SIMPLIFIED_CHINESE: return 'B'; - case 6: + case Language::TRADITIONAL_CHINESE: return 'T'; - case 7: + case Language::KOREAN: return 'K'; default: return 'E'; diff --git a/src/Text.hh b/src/Text.hh index 2f854996..7ae0646d 100644 --- a/src/Text.hh +++ b/src/Text.hh @@ -11,6 +11,7 @@ #include #include +#include "StaticGameData.hh" #include "Types.hh" #define check_struct_size(StructT, Size) \ @@ -81,11 +82,11 @@ extern TextTranscoder tt_utf8_to_utf16; extern TextTranscoder tt_ascii_to_utf8; extern TextTranscoder tt_utf8_to_ascii; -std::string tt_encode_marked_optional(const std::string& utf8, uint8_t default_language, bool is_utf16); -std::string tt_encode_marked(const std::string& utf8, uint8_t default_language, bool is_utf16); -std::string tt_decode_marked(const std::string& data, uint8_t default_language, bool is_utf16); +std::string tt_encode_marked_optional(const std::string& utf8, Language default_language, bool is_utf16); +std::string tt_encode_marked(const std::string& utf8, Language default_language, bool is_utf16); +std::string tt_decode_marked(const std::string& data, Language default_language, bool is_utf16); -char marker_for_language_code(uint8_t language_code); +char marker_for_language(Language language); bool is_language_marker_sjis_8859(char marker); bool is_language_marker_utf16(char marker); @@ -467,7 +468,7 @@ struct pstring { pstring(const pstring& other) { memcpy(this->data, other.data, Bytes); } - pstring(const std::string& s, uint8_t language) { + pstring(const std::string& s, Language language) { this->encode(s, language); } pstring(pstring&& other) = delete; @@ -485,7 +486,7 @@ struct pstring { } pstring& operator=(pstring&& s) = delete; - void encode(const std::string& s, uint8_t client_language = 1) { + void encode(const std::string& s, Language client_language = Language::ENGLISH) { try { switch (Encoding) { case TextEncoding::CHALLENGE8: @@ -513,7 +514,7 @@ struct pstring { break; } else if (s.size() <= 2 || s[0] != '\t' || s[1] == 'C') { std::string to_encode = "\t"; - to_encode += marker_for_language_code(client_language); + to_encode += marker_for_language(client_language); to_encode += s; auto ret = tt_utf8_to_utf16(this->data, Bytes, to_encode.data(), to_encode.size(), true); this->clear_after_bytes(ret.bytes_written); @@ -536,7 +537,7 @@ struct pstring { break; } case TextEncoding::MARKED: { - if (client_language == 0) { + if (client_language == Language::JAPANESE) { try { auto ret = tt_utf8_to_sega_sjis(this->data, Bytes, s.data(), s.size(), true); this->clear_after_bytes(ret.bytes_written); @@ -592,7 +593,7 @@ struct pstring { } } - std::string decode(uint8_t client_language = 1) const { + std::string decode(Language client_language = Language::ENGLISH) const { try { switch (Encoding) { case TextEncoding::CHALLENGE8: { @@ -623,16 +624,16 @@ struct pstring { size_t offset = 0; if (this->data[0] == '\t') { if (this->data[1] == 'J') { - client_language = 0; + client_language = Language::JAPANESE; offset = 2; } else if (this->data[1] != 'C') { - client_language = 1; + client_language = Language::ENGLISH; offset = 2; } } - return client_language - ? tt_8859_to_utf8(&this->data[offset], this->used_chars_8() - offset) - : tt_sega_sjis_to_utf8(&this->data[offset], this->used_chars_8() - offset); + return (client_language == Language::JAPANESE) + ? tt_sega_sjis_to_utf8(&this->data[offset], this->used_chars_8() - offset) + : tt_8859_to_utf8(&this->data[offset], this->used_chars_8() - offset); } default: throw std::logic_error("unknown text encoding"); @@ -650,7 +651,7 @@ struct pstring { return (memcmp(this->data, other.data, Bytes) != 0); } - bool eq(const std::string& other, uint8_t language = 1) const { + bool eq(const std::string& other, Language language = Language::ENGLISH) const { return this->decode(language) == other; } diff --git a/src/TextIndex.cc b/src/TextIndex.cc index c186b279..21659f47 100644 --- a/src/TextIndex.cc +++ b/src/TextIndex.cc @@ -454,54 +454,54 @@ TextIndex::TextIndex( : log("[TextIndex] ", static_game_data_log.min_level) { if (!directory.empty()) { auto add_version = [&](Version version, const string& subdirectory, function(const string&, bool)> make_set) -> void { - static const map bintext_filenames({ - {"TextJapanese.pr2", 0x00}, - {"TextEnglish.pr2", 0x01}, - {"TextGerman.pr2", 0x02}, - {"TextFrench.pr2", 0x03}, - {"TextSpanish.pr2", 0x04}, + static const map bintext_filenames({ + {"TextJapanese.pr2", Language::JAPANESE}, + {"TextEnglish.pr2", Language::ENGLISH}, + {"TextGerman.pr2", Language::GERMAN}, + {"TextFrench.pr2", Language::FRENCH}, + {"TextSpanish.pr2", Language::SPANISH}, }); - static const map unitext_filenames({ - {"unitxt_j.prs", 0x00}, // PC/BB Japanese - {"unitxt_e.prs", 0x01}, // PC/BB English - {"unitxt_g.prs", 0x02}, // PC/BB German - {"unitxt_f.prs", 0x03}, // PC/BB French - {"unitxt_s.prs", 0x04}, // PC/BB Spanish - {"unitxt_b.prs", 0x05}, // PC Simplified Chinese - {"unitxt_cs.prs", 0x05}, // BB Simplified Chinese - {"unitxt_t.prs", 0x06}, // PC Traditional Chinese - {"unitxt_ct.prs", 0x06}, // BB Traditional Chinese - {"unitxt_k.prs", 0x07}, // PC Korean - {"unitxt_h.prs", 0x07}, // BB Korean + static const map unitext_filenames({ + {"unitxt_j.prs", Language::JAPANESE}, // PC/BB Japanese + {"unitxt_e.prs", Language::ENGLISH}, // PC/BB English + {"unitxt_g.prs", Language::GERMAN}, // PC/BB German + {"unitxt_f.prs", Language::FRENCH}, // PC/BB French + {"unitxt_s.prs", Language::SPANISH}, // PC/BB Spanish + {"unitxt_b.prs", Language::SIMPLIFIED_CHINESE}, // PC Simplified Chinese + {"unitxt_cs.prs", Language::SIMPLIFIED_CHINESE}, // BB Simplified Chinese + {"unitxt_t.prs", Language::TRADITIONAL_CHINESE}, // PC Traditional Chinese + {"unitxt_ct.prs", Language::TRADITIONAL_CHINESE}, // BB Traditional Chinese + {"unitxt_k.prs", Language::KOREAN}, // PC Korean + {"unitxt_h.prs", Language::KOREAN}, // BB Korean }); if (!uses_utf16(version)) { - for (const auto& it : bintext_filenames) { - string file_path = directory + "/" + subdirectory + "/" + it.first; + for (const auto& [base_filename, language] : bintext_filenames) { + string file_path = directory + "/" + subdirectory + "/" + base_filename; string json_path = file_path + ".json"; if (std::filesystem::is_regular_file(json_path)) { - this->log.debug_f("Loading {} {} JSON text set from {}", phosg::name_for_enum(version), char_for_language_code(it.second), json_path); - this->add_set(version, it.second, make_shared(phosg::JSON::parse(phosg::load_file(json_path)))); + this->log.debug_f("Loading {} {} JSON text set from {}", phosg::name_for_enum(version), name_for_language(language), json_path); + this->add_set(version, language, make_shared(phosg::JSON::parse(phosg::load_file(json_path)))); } else if (std::filesystem::is_regular_file(file_path)) { - this->log.debug_f("Loading {} {} binary text set from {}", phosg::name_for_enum(version), char_for_language_code(it.second), file_path); - this->add_set(version, it.second, make_set(phosg::load_file(file_path), it.second == 0)); + this->log.debug_f("Loading {} {} binary text set from {}", phosg::name_for_enum(version), name_for_language(language), file_path); + this->add_set(version, language, make_set(phosg::load_file(file_path), language == Language::JAPANESE)); } } } else { - for (const auto& it : unitext_filenames) { - string file_path = directory + "/" + subdirectory + "/" + it.first; + for (const auto& [base_filename, language] : unitext_filenames) { + string file_path = directory + "/" + subdirectory + "/" + base_filename; string json_path = file_path + ".json"; if (std::filesystem::is_regular_file(json_path)) { - this->log.debug_f("Loading {} {} JSON text set from {}", phosg::name_for_enum(version), char_for_language_code(it.second), json_path); - this->add_set(version, it.second, make_shared(phosg::JSON::parse(phosg::load_file(json_path)))); + this->log.debug_f("Loading {} {} JSON text set from {}", phosg::name_for_enum(version), name_for_language(language), json_path); + this->add_set(version, language, make_shared(phosg::JSON::parse(phosg::load_file(json_path)))); } else { - auto patch_file = get_patch_file ? get_patch_file(version, it.first) : nullptr; + auto patch_file = get_patch_file ? get_patch_file(version, base_filename) : nullptr; if (patch_file) { - this->log.debug_f("Loading {} {} Unicode text set from {} in patch tree", phosg::name_for_enum(version), char_for_language_code(it.second), it.first); - this->add_set(version, it.second, make_set(*patch_file, it.second == 0)); + this->log.debug_f("Loading {} {} Unicode text set from {} in patch tree", phosg::name_for_enum(version), name_for_language(language), base_filename); + this->add_set(version, language, make_set(*patch_file, language == Language::JAPANESE)); } else { if (std::filesystem::is_regular_file(file_path)) { - this->log.debug_f("Loading {} {} Unicode text set from {}", phosg::name_for_enum(version), char_for_language_code(it.second), file_path); - this->add_set(version, it.second, make_set(phosg::load_file(file_path), it.second == 0)); + this->log.debug_f("Loading {} {} Unicode text set from {}", phosg::name_for_enum(version), name_for_language(language), file_path); + this->add_set(version, language, make_set(phosg::load_file(file_path), language == Language::JAPANESE)); } } } @@ -531,26 +531,26 @@ TextIndex::TextIndex( } } -void TextIndex::add_set(Version version, uint8_t language, std::shared_ptr ts) { +void TextIndex::add_set(Version version, Language language, std::shared_ptr ts) { this->sets[this->key_for_set(version, language)] = ts; } -void TextIndex::delete_set(Version version, uint8_t language) { +void TextIndex::delete_set(Version version, Language language) { this->sets.erase(this->key_for_set(version, language)); } -const std::string& TextIndex::get(Version version, uint8_t language, size_t collection_index, size_t string_index) const { +const std::string& TextIndex::get(Version version, Language language, size_t collection_index, size_t string_index) const { return this->get(version, language)->get(collection_index, string_index); } -const std::vector& TextIndex::get(Version version, uint8_t language, size_t collection_index) const { +const std::vector& TextIndex::get(Version version, Language language, size_t collection_index) const { return this->get(version, language)->get(collection_index); } -std::shared_ptr TextIndex::get(Version version, uint8_t language) const { +std::shared_ptr TextIndex::get(Version version, Language language) const { return this->sets.at(this->key_for_set(version, language)); } -uint32_t TextIndex::key_for_set(Version version, uint8_t language) { - return (static_cast(version) << 8) | language; +uint32_t TextIndex::key_for_set(Version version, Language language) { + return (static_cast(version) << 8) | static_cast(language); } diff --git a/src/TextIndex.hh b/src/TextIndex.hh index 3220d576..479618f3 100644 --- a/src/TextIndex.hh +++ b/src/TextIndex.hh @@ -96,15 +96,15 @@ public: std::function(Version, const std::string&)> get_patch_file = nullptr); ~TextIndex() = default; - void add_set(Version version, uint8_t language, std::shared_ptr ts); - void delete_set(Version version, uint8_t language); + void add_set(Version version, Language language, std::shared_ptr ts); + void delete_set(Version version, Language language); - const std::string& get(Version version, uint8_t language, size_t collection_index, size_t string_index) const; - const std::vector& get(Version version, uint8_t language, size_t collection_index) const; - std::shared_ptr get(Version version, uint8_t language) const; + const std::string& get(Version version, Language language, size_t collection_index, size_t string_index) const; + const std::vector& get(Version version, Language language, size_t collection_index) const; + std::shared_ptr get(Version version, Language language) const; protected: - static uint32_t key_for_set(Version version, uint8_t language); + static uint32_t key_for_set(Version version, Language language); phosg::PrefixedLogger log; std::unordered_map> sets; diff --git a/system/quests/download/q050-d1-e.bin.txt b/system/quests/download/q050-d1-e.bin.txt index b6817366..1ac3d6ec 100755 --- a/system/quests/download/q050-d1-e.bin.txt +++ b/system/quests/download/q050-d1-e.bin.txt @@ -1,6 +1,6 @@ .version DC_V1 .quest_num 50 -.language 1 +.language E .name "Soul of a Blacksmith" .short_desc "A blacksmith\nwants to make\na weapon using\nRagol\'s unknown\nmaterials." .long_desc "Client: Ozwald\nQuest:\nI want to make a new\nweapon using unknown\nmaterials on Ragol.\nReward: 2500 Meseta" diff --git a/system/quests/download/q052-d1-e.bin.txt b/system/quests/download/q052-d1-e.bin.txt index 6afcc2a5..159c1b02 100755 --- a/system/quests/download/q052-d1-e.bin.txt +++ b/system/quests/download/q052-d1-e.bin.txt @@ -1,6 +1,6 @@ .version DC_V1 .quest_num 52 -.language 1 +.language E .name "The Retired Hunter" .short_desc "I will kill\n10000 monsters\nbefore I die!" .long_desc "Client: Donoph\nQuest:\n An old hunter, Donoph,\n is about to die.\n Defeat 99 monsters to\n fulfill his dream.\nReward: 8000 Meseta\n" diff --git a/system/quests/hidden/q88530-gc-e.bin.txt b/system/quests/hidden/q88530-gc-e.bin.txt index 178e8297..acfa47aa 100644 --- a/system/quests/hidden/q88530-gc-e.bin.txt +++ b/system/quests/hidden/q88530-gc-e.bin.txt @@ -1,6 +1,6 @@ .version GC_V3 .quest_num 88530 -.language 1 +.language E .episode Episode1 .name "GC v1.2 USA patch enabler" .short_desc "" diff --git a/system/quests/hidden/q88531-gc-e.bin.txt b/system/quests/hidden/q88531-gc-e.bin.txt index 40f85028..203d038f 100644 --- a/system/quests/hidden/q88531-gc-e.bin.txt +++ b/system/quests/hidden/q88531-gc-e.bin.txt @@ -1,6 +1,6 @@ .version GC_V3 .quest_num 88531 -.language 0 +.language J .episode Episode1 .name "GC v1.5 JP patch enabler" .short_desc "" diff --git a/system/quests/hidden/q88532-gc3-e.bin.txt b/system/quests/hidden/q88532-gc3-e.bin.txt index cd73e3cc..55dcb37b 100644 --- a/system/quests/hidden/q88532-gc3-e.bin.txt +++ b/system/quests/hidden/q88532-gc3-e.bin.txt @@ -1,6 +1,6 @@ .version GC_EP3 .quest_num 88532 -.language 1 +.language E .episode Episode1 .name "GC Ep3 USA patch enabler" .short_desc "" diff --git a/system/quests/hidden/q88533-gc3-e.bin.txt b/system/quests/hidden/q88533-gc3-e.bin.txt index fee921a8..d7992493 100644 --- a/system/quests/hidden/q88533-gc3-e.bin.txt +++ b/system/quests/hidden/q88533-gc3-e.bin.txt @@ -1,6 +1,6 @@ .version GC_EP3 .quest_num 88533 -.language 1 +.language E .episode Episode1 .name "GC Ep3 EU patch enabler" .short_desc "" diff --git a/system/quests/retrieval/q058-gc-e.bin.txt b/system/quests/retrieval/q058-gc-e.bin.txt index f2e067c4..200ccd98 100644 --- a/system/quests/retrieval/q058-gc-e.bin.txt +++ b/system/quests/retrieval/q058-gc-e.bin.txt @@ -27,15 +27,15 @@ // versions (DC, GC, and XB), this affects how strings are encoded - Japanese // uses Shift-JIS and other languages use ISO8859. (On PC V2 and BB, UTF-16 is // used for strings in all languages.) The language values are: -// 0 = Japanese -// 1 = English -// 2 = German -// 3 = French -// 4 = Spanish -// 5 = Chinese (simplified) -// 6 = Chinese (traditional) -// 7 = Korean -.language 1 +// J = Japanese +// E = English +// G = German +// F = French +// S = Spanish +// B = Chinese (simplified) +// T = Chinese (traditional) +// K = Korean +.language E // The .episode directive specifies the quest's episode. The server ignores this // if a set_episode or set_episode2 opcode is present in the code following the