From 3743d0a15605bad2101498e645eba7e49140be63 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Wed, 29 Nov 2023 22:22:19 -0800 Subject: [PATCH] implement quest unlock flags --- README.md | 2 +- src/CommandFormats.hh | 2 +- src/License.hh | 29 +++---- src/Lobby.cc | 27 +++++++ src/Lobby.hh | 2 + src/PlayerSubordinates.cc | 92 +++++++++++------------ src/PlayerSubordinates.hh | 34 +++++++++ src/Quest.cc | 88 ++++++++++++++-------- src/Quest.hh | 31 ++++++-- src/ReceiveCommands.cc | 11 ++- src/ReceiveSubcommands.cc | 12 ++- src/SaveFileFormats.hh | 7 +- src/SendCommands.cc | 8 +- system/quests/battle/b88001.json | 67 +++++++++++------ system/quests/battle/b88002.json | 44 +++++------ system/quests/battle/b88003.json | 44 +++++------ system/quests/battle/b88004.json | 46 ++++++------ system/quests/battle/b88005.json | 40 +++++----- system/quests/battle/b88006.json | 44 +++++------ system/quests/battle/b88007.json | 46 ++++++------ system/quests/battle/b88008.json | 46 ++++++------ system/quests/challenge-ep1/c88101.json | 2 +- system/quests/challenge-ep1/c88102.json | 2 +- system/quests/challenge-ep1/c88103.json | 2 +- system/quests/challenge-ep1/c88104.json | 2 +- system/quests/challenge-ep1/c88105.json | 2 +- system/quests/challenge-ep1/c88106.json | 2 +- system/quests/challenge-ep1/c88107.json | 2 +- system/quests/challenge-ep1/c88108.json | 2 +- system/quests/challenge-ep1/c88109.json | 2 +- system/quests/challenge-ep2/d88201.json | 2 +- system/quests/challenge-ep2/d88202.json | 2 +- system/quests/challenge-ep2/d88203.json | 2 +- system/quests/challenge-ep2/d88204.json | 2 +- system/quests/challenge-ep2/d88205.json | 2 +- system/quests/government-ep1/q402-bb.json | 3 + system/quests/government-ep1/q403-bb.json | 3 + system/quests/government-ep1/q404-bb.json | 3 + system/quests/government-ep1/q405-bb.json | 3 + system/quests/government-ep1/q406-bb.json | 3 + system/quests/government-ep1/q407-bb.json | 3 + system/quests/government-ep1/q408-bb.json | 3 + system/quests/government-ep1/q409-bb.json | 3 + system/quests/government-ep1/q410-bb.json | 3 + system/quests/government-ep1/q411-bb.json | 3 + system/quests/government-ep1/q412-bb.json | 3 + system/quests/government-ep1/q413-bb.json | 3 + system/quests/government-ep1/q414-bb.json | 3 + system/quests/government-ep1/q415-bb.json | 3 + system/quests/government-ep2/q452-bb.json | 3 + system/quests/government-ep2/q453-bb.json | 3 + system/quests/government-ep2/q454-bb.json | 3 + system/quests/government-ep2/q455-bb.json | 3 + system/quests/government-ep2/q456-bb.json | 3 + system/quests/government-ep2/q457-bb.json | 3 + system/quests/government-ep2/q458-bb.json | 3 + system/quests/government-ep2/q459-bb.json | 3 + system/quests/government-ep2/q460-bb.json | 3 + system/quests/government-ep2/q461-bb.json | 3 + system/quests/government-ep2/q462-bb.json | 3 + system/quests/government-ep2/q463-bb.json | 3 + system/quests/government-ep2/q464-bb.json | 3 + system/quests/government-ep2/q465-bb.json | 3 + system/quests/government-ep2/q466-bb.json | 3 + system/quests/government-ep2/q467-bb.json | 3 + system/quests/government-ep2/q468-bb.json | 3 + system/quests/government-ep4/q702-bb.json | 3 + system/quests/government-ep4/q703-bb.json | 3 + system/quests/government-ep4/q704-bb.json | 3 + system/quests/government-ep4/q705-bb.json | 3 + system/quests/government-ep4/q706-bb.json | 3 + system/quests/government-ep4/q707-bb.json | 3 + system/quests/government-ep4/q708-bb.json | 3 + system/quests/team/q709.json | 3 + system/quests/team/q710.json | 3 + 75 files changed, 564 insertions(+), 306 deletions(-) create mode 100644 system/quests/government-ep1/q402-bb.json create mode 100644 system/quests/government-ep1/q403-bb.json create mode 100644 system/quests/government-ep1/q404-bb.json create mode 100644 system/quests/government-ep1/q405-bb.json create mode 100644 system/quests/government-ep1/q406-bb.json create mode 100644 system/quests/government-ep1/q407-bb.json create mode 100644 system/quests/government-ep1/q408-bb.json create mode 100644 system/quests/government-ep1/q409-bb.json create mode 100644 system/quests/government-ep1/q410-bb.json create mode 100644 system/quests/government-ep1/q411-bb.json create mode 100644 system/quests/government-ep1/q412-bb.json create mode 100644 system/quests/government-ep1/q413-bb.json create mode 100644 system/quests/government-ep1/q414-bb.json create mode 100644 system/quests/government-ep1/q415-bb.json create mode 100644 system/quests/government-ep2/q452-bb.json create mode 100644 system/quests/government-ep2/q453-bb.json create mode 100644 system/quests/government-ep2/q454-bb.json create mode 100644 system/quests/government-ep2/q455-bb.json create mode 100644 system/quests/government-ep2/q456-bb.json create mode 100644 system/quests/government-ep2/q457-bb.json create mode 100644 system/quests/government-ep2/q458-bb.json create mode 100644 system/quests/government-ep2/q459-bb.json create mode 100644 system/quests/government-ep2/q460-bb.json create mode 100644 system/quests/government-ep2/q461-bb.json create mode 100644 system/quests/government-ep2/q462-bb.json create mode 100644 system/quests/government-ep2/q463-bb.json create mode 100644 system/quests/government-ep2/q464-bb.json create mode 100644 system/quests/government-ep2/q465-bb.json create mode 100644 system/quests/government-ep2/q466-bb.json create mode 100644 system/quests/government-ep2/q467-bb.json create mode 100644 system/quests/government-ep2/q468-bb.json create mode 100644 system/quests/government-ep4/q702-bb.json create mode 100644 system/quests/government-ep4/q703-bb.json create mode 100644 system/quests/government-ep4/q704-bb.json create mode 100644 system/quests/government-ep4/q705-bb.json create mode 100644 system/quests/government-ep4/q706-bb.json create mode 100644 system/quests/government-ep4/q707-bb.json create mode 100644 system/quests/government-ep4/q708-bb.json create mode 100644 system/quests/team/q709.json create mode 100644 system/quests/team/q710.json diff --git a/README.md b/README.md index c156bfd2..5b1f6151 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ Within the category directories, quest files should be named like `q###-VERSION- For .dat files, the `LANGUAGE` token may be omitted. If it's present, then that .dat file will only be used for that language of the quest; if omitted, then that .dat file will be used for all languages of the quest. -Some quests (mostly battle and challenge mode quests) have additional JSON metadata files that describe how the server should handle them. These metadata files are generally named similarly to their .bin and .dat counterparts, except the `VERSION` token may also be omitted if the metadata applies to all versions of the quest on all PSO versions. +Some quests (mostly battle and challenge mode quests) have additional JSON metadata files that describe how the server should handle them. This includes flags that can be used to hide the quest unless a preceding quest has been cleared, or to hide the quest unless purchased as a team reward. These metadata files are generally named similarly to their .bin and .dat counterparts, except the `VERSION` token may also be omitted if the metadata applies to all versions of the quest on all PSO versions. See system/quests/battle/b88001.json for documentation on the exact format of the JSON file. Some quests may also include a .pvr file, which contains an image used in the quest. These files are named similarly to their .bin and .dat counterparts. diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 3dab6650..d36aa99f 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -4626,7 +4626,7 @@ struct G_SyncFlagState_6x6E_Decompressed { struct G_SetQuestFlags_6x6F { G_UnusedHeader header; - parray, 4> quest_flags_by_difficulty; + QuestFlags quest_flags; } __packed__; // 6x70: Sync player disp data and inventory (used while loading into game) diff --git a/src/License.hh b/src/License.hh index d89735eb..015a3644 100644 --- a/src/License.hh +++ b/src/License.hh @@ -13,21 +13,22 @@ class LicenseIndex; struct License { enum Flag : uint32_t { // clang-format off - KICK_USER = 0x00000001, - BAN_USER = 0x00000002, - SILENCE_USER = 0x00000004, - CHANGE_LOBBY_INFO = 0x00000008, - CHANGE_EVENT = 0x00000010, - ANNOUNCE = 0x00000020, - FREE_JOIN_GAMES = 0x00000040, - UNLOCK_GAMES = 0x00000080, - DEBUG = 0x01000000, - CHEAT_ANYWHERE = 0x02000000, - MODERATOR = 0x00000007, - ADMINISTRATOR = 0x000000FF, - ROOT = 0x7FFFFFFF, + KICK_USER = 0x00000001, + BAN_USER = 0x00000002, + SILENCE_USER = 0x00000004, + CHANGE_LOBBY_INFO = 0x00000008, + CHANGE_EVENT = 0x00000010, + ANNOUNCE = 0x00000020, + FREE_JOIN_GAMES = 0x00000040, + UNLOCK_GAMES = 0x00000080, + DEBUG = 0x01000000, + CHEAT_ANYWHERE = 0x02000000, + DISABLE_QUEST_REQUIREMENTS = 0x04000000, + MODERATOR = 0x00000007, + ADMINISTRATOR = 0x000000FF, + ROOT = 0x7FFFFFFF, - UNUSED_BITS = 0x7CFFFF00, + UNUSED_BITS = 0x78FFFF00, // clang-format on }; diff --git a/src/Lobby.cc b/src/Lobby.cc index 3f90b539..d8e07e8a 100644 --- a/src/Lobby.cc +++ b/src/Lobby.cc @@ -401,3 +401,30 @@ unordered_map> Lobby::clients_by_serial_number() co } return ret; } + +QuestIndex::IncludeCondition Lobby::quest_include_condition() const { + return [this](shared_ptr q) -> bool { + if (q->require_flag >= 0) { + for (const auto& lc : this->clients) { + if (lc && !lc->game_data.character()->quest_flags.get(this->difficulty, q->require_flag)) { + return false; + } + } + } + if (!q->require_team_reward_key.empty()) { + // TODO: Technically we should check that all clients are on the SAME + // team, but I'm lazy (for now), so we allow clients on different teams + // to access the quest as long as all their teams have the quest reward. + for (const auto& lc : this->clients) { + if (!lc) { + continue; + } + auto team = lc->team(); + if (!team || !team->has_reward(q->require_team_reward_key)) { + return false; + } + } + } + return true; + }; +} diff --git a/src/Lobby.hh b/src/Lobby.hh index 46726753..34f75302 100644 --- a/src/Lobby.hh +++ b/src/Lobby.hh @@ -192,6 +192,8 @@ struct Lobby : public std::enable_shared_from_this { void on_item_id_generated_externally(uint32_t item_id); void assign_inventory_item_ids(std::shared_ptr c); + QuestIndex::IncludeCondition quest_include_condition() const; + static uint8_t game_event_for_lobby_event(uint8_t lobby_event); std::unordered_map> clients_by_serial_number() const; diff --git a/src/PlayerSubordinates.cc b/src/PlayerSubordinates.cc index d1e24b57..4fff22ec 100644 --- a/src/PlayerSubordinates.cc +++ b/src/PlayerSubordinates.cc @@ -686,59 +686,59 @@ size_t PlayerBank::find_item(uint32_t item_id) { BattleRules::BattleRules(const JSON& json) { static const JSON empty_list = JSON::list(); - this->tech_disk_mode = json.get_enum("tech_disk_mode", this->tech_disk_mode); - this->weapon_and_armor_mode = json.get_enum("weapon_and_armor_mode", this->weapon_and_armor_mode); - this->mag_mode = json.get_enum("mag_mode", this->mag_mode); - this->tool_mode = json.get_enum("tool_mode", this->tool_mode); - this->trap_mode = json.get_enum("trap_mode", this->trap_mode); - this->unused_F817 = json.get_int("unused_F817", this->unused_F817); - this->respawn_mode = json.get_int("respawn_mode", this->respawn_mode); - this->replace_char = json.get_int("replace_char", this->replace_char); - this->drop_weapon = json.get_int("drop_weapon", this->drop_weapon); - this->is_teams = json.get_int("is_teams", this->is_teams); - this->hide_target_reticle = json.get_int("hide_target_reticle", this->hide_target_reticle); - this->meseta_mode = json.get_enum("meseta_mode", this->meseta_mode); - this->death_level_up = json.get_int("death_level_up", this->death_level_up); - const JSON& trap_counts_json = json.get("trap_counts", empty_list); + this->tech_disk_mode = json.get_enum("TechDiskMode", this->tech_disk_mode); + this->weapon_and_armor_mode = json.get_enum("WeaponAndArmorMode", this->weapon_and_armor_mode); + this->mag_mode = json.get_enum("MagMode", this->mag_mode); + this->tool_mode = json.get_enum("ToolMode", this->tool_mode); + this->trap_mode = json.get_enum("TrapMode", this->trap_mode); + this->unused_F817 = json.get_int("UnusedF817", this->unused_F817); + this->respawn_mode = json.get_int("RespawnMode", this->respawn_mode); + this->replace_char = json.get_int("ReplaceChar", this->replace_char); + this->drop_weapon = json.get_int("DropWeapon", this->drop_weapon); + this->is_teams = json.get_int("IsTeams", this->is_teams); + this->hide_target_reticle = json.get_int("HideTargetReticle", this->hide_target_reticle); + this->meseta_mode = json.get_enum("MesetaMode", this->meseta_mode); + this->death_level_up = json.get_int("DeathLevelUp", this->death_level_up); + const JSON& trap_counts_json = json.get("TrapCounts", empty_list); for (size_t z = 0; z < trap_counts_json.size(); z++) { this->trap_counts[z] = trap_counts_json.at(z).as_int(); } - this->enable_sonar = json.get_int("enable_sonar", this->enable_sonar); - this->sonar_count = json.get_int("sonar_count", this->sonar_count); - this->forbid_scape_dolls = json.get_int("forbid_scape_dolls", this->forbid_scape_dolls); - this->lives = json.get_int("lives", this->lives); - this->max_tech_level = json.get_int("max_tech_level", this->max_tech_level); - this->char_level = json.get_int("char_level", this->char_level); - this->time_limit = json.get_int("time_limit", this->time_limit); - this->death_tech_level_up = json.get_int("death_tech_level_up", this->death_tech_level_up); - this->box_drop_area = json.get_int("box_drop_area", this->box_drop_area); + this->enable_sonar = json.get_int("EnableSonar", this->enable_sonar); + this->sonar_count = json.get_int("SonarCount", this->sonar_count); + this->forbid_scape_dolls = json.get_int("ForbidScapeDolls", this->forbid_scape_dolls); + this->lives = json.get_int("Lives", this->lives); + this->max_tech_level = json.get_int("MaxTechLevel", this->max_tech_level); + this->char_level = json.get_int("CharLevel", this->char_level); + this->time_limit = json.get_int("TimeLimit", this->time_limit); + this->death_tech_level_up = json.get_int("DeathTechLevelUp", this->death_tech_level_up); + this->box_drop_area = json.get_int("BoxDropArea", this->box_drop_area); } JSON BattleRules::json() const { return JSON::dict({ - {"tech_disk_mode", this->tech_disk_mode}, - {"weapon_and_armor_mode", this->weapon_and_armor_mode}, - {"mag_mode", this->mag_mode}, - {"tool_mode", this->tool_mode}, - {"trap_mode", this->trap_mode}, - {"unused_F817", this->unused_F817}, - {"respawn_mode", this->respawn_mode}, - {"replace_char", this->replace_char}, - {"drop_weapon", this->drop_weapon}, - {"is_teams", this->is_teams}, - {"hide_target_reticle", this->hide_target_reticle}, - {"meseta_mode", this->meseta_mode}, - {"death_level_up", this->death_level_up}, - {"trap_counts", JSON::list({this->trap_counts[0], this->trap_counts[1], this->trap_counts[2], this->trap_counts[3]})}, - {"enable_sonar", this->enable_sonar}, - {"sonar_count", this->sonar_count}, - {"forbid_scape_dolls", this->forbid_scape_dolls}, - {"lives", this->lives.load()}, - {"max_tech_level", this->max_tech_level.load()}, - {"char_level", this->char_level.load()}, - {"time_limit", this->time_limit.load()}, - {"death_tech_level_up", this->death_tech_level_up.load()}, - {"box_drop_area", this->box_drop_area.load()}, + {"TechDiskMode", this->tech_disk_mode}, + {"WeaponAndArmorMode", this->weapon_and_armor_mode}, + {"MagMode", this->mag_mode}, + {"ToolMode", this->tool_mode}, + {"TrapMode", this->trap_mode}, + {"UnusedF817", this->unused_F817}, + {"RespawnMode", this->respawn_mode}, + {"ReplaceChar", this->replace_char}, + {"DropWeapon", this->drop_weapon}, + {"IsTeams", this->is_teams}, + {"HideTargetReticle", this->hide_target_reticle}, + {"MesetaMode", this->meseta_mode}, + {"DeathLevelUp", this->death_level_up}, + {"TrapCounts", JSON::list({this->trap_counts[0], this->trap_counts[1], this->trap_counts[2], this->trap_counts[3]})}, + {"EnableSonar", this->enable_sonar}, + {"SonarCount", this->sonar_count}, + {"ForbidScapeDolls", this->forbid_scape_dolls}, + {"Lives", this->lives.load()}, + {"MaxTechLevel", this->max_tech_level.load()}, + {"CharLevel", this->char_level.load()}, + {"TimeLimit", this->time_limit.load()}, + {"DeathTechLevelUp", this->death_tech_level_up.load()}, + {"BoxDropArea", this->box_drop_area.load()}, }); } diff --git a/src/PlayerSubordinates.hh b/src/PlayerSubordinates.hh index cfdfa335..14dc4dab 100644 --- a/src/PlayerSubordinates.hh +++ b/src/PlayerSubordinates.hh @@ -483,6 +483,40 @@ inline PlayerDispDataBB convert_player_disp_data( return src; } +struct QuestFlagsForDifficulty { + parray data; + + inline bool get(uint16_t flag_index) const { + size_t byte_index = flag_index >> 3; + uint8_t mask = 0x80 >> (flag_index & 7); + return !!(this->data[byte_index] & mask); + } + inline void set(uint16_t flag_index) { + size_t byte_index = flag_index >> 3; + uint8_t mask = 0x80 >> (flag_index & 7); + this->data[byte_index] |= mask; + } + inline void clear(uint16_t flag_index) { + size_t byte_index = flag_index >> 3; + uint8_t mask = 0x80 >> (flag_index & 7); + this->data[byte_index] &= (~mask); + } +} __attribute__((packed)); + +struct QuestFlags { + parray data; + + inline bool get(uint8_t difficulty, uint16_t flag_index) const { + return this->data[difficulty].get(flag_index); + } + inline void set(uint8_t difficulty, uint16_t flag_index) { + this->data[difficulty].set(flag_index); + } + inline void clear(uint8_t difficulty, uint16_t flag_index) { + this->data[difficulty].clear(flag_index); + } +} __attribute__((packed)); + struct BattleRules { enum class TechDiskMode : uint8_t { ALLOW = 0, diff --git a/src/Quest.cc b/src/Quest.cc index a043aaa5..a9fbb2c0 100644 --- a/src/Quest.cc +++ b/src/Quest.cc @@ -204,7 +204,9 @@ VersionedQuest::VersionedQuest( std::shared_ptr dat_contents, std::shared_ptr pvr_contents, std::shared_ptr battle_rules, - ssize_t challenge_template_index) + ssize_t challenge_template_index, + int16_t require_flag, + const string& require_team_reward_key) : quest_number(quest_number), category_id(category_id), episode(Episode::NONE), @@ -216,7 +218,9 @@ VersionedQuest::VersionedQuest( dat_contents(dat_contents), pvr_contents(pvr_contents), battle_rules(battle_rules), - challenge_template_index(challenge_template_index) { + challenge_template_index(challenge_template_index), + require_flag(require_flag), + require_team_reward_key(require_team_reward_key) { auto bin_decompressed = prs_decompress(*this->bin_contents); @@ -373,7 +377,9 @@ Quest::Quest(shared_ptr initial_version) joinable(initial_version->joinable), name(initial_version->name), battle_rules(initial_version->battle_rules), - challenge_template_index(initial_version->challenge_template_index) { + challenge_template_index(initial_version->challenge_template_index), + require_flag(initial_version->require_flag), + require_team_reward_key(initial_version->require_team_reward_key) { this->versions.emplace(this->versions_key(initial_version->version, initial_version->language), initial_version); } @@ -403,6 +409,12 @@ void Quest::add_version(shared_ptr vq) { if (this->challenge_template_index != vq->challenge_template_index) { throw runtime_error("quest version has different challenge template index"); } + if (this->require_flag != vq->require_flag) { + throw runtime_error("quest version has different required flag"); + } + if (this->require_team_reward_key != vq->require_team_reward_key) { + throw runtime_error("quest version has different required team reward key"); + } this->versions.emplace(this->versions_key(vq->version, vq->language), vq); } @@ -627,6 +639,8 @@ QuestIndex::QuestIndex( JSON metadata_json = nullptr; shared_ptr battle_rules; ssize_t challenge_template_index = -1; + int16_t require_flag = -1; + string require_team_reward_key; try { json_filename = basename; metadata_json = JSON::parse(*json_files.at(json_filename)); @@ -644,11 +658,16 @@ QuestIndex::QuestIndex( } if (!metadata_json.is_null()) { try { - battle_rules.reset(new BattleRules(metadata_json.at("battle_rules"))); + battle_rules.reset(new BattleRules(metadata_json.at("BattleRules"))); } catch (const out_of_range&) { } try { - challenge_template_index = metadata_json.at("challenge_template_index").as_int(); + challenge_template_index = metadata_json.at("ChallengeTemplateIndex").as_int(); + } catch (const out_of_range&) { + } + require_flag = metadata_json.get_int("RequireFlag", -1); + try { + require_team_reward_key = metadata_json.get_int("RequireTeamRewardKey", -1); } catch (const out_of_range&) { } } @@ -662,7 +681,9 @@ QuestIndex::QuestIndex( dat_contents, pvr_contents, battle_rules, - challenge_template_index)); + challenge_template_index, + require_flag, + require_team_reward_key)); auto category_name = this->category_index->at(vq->category_id)->name; string dat_str = dat_filename.empty() ? "" : (" with layout from " + dat_filename + ".dat"); @@ -681,7 +702,9 @@ QuestIndex::QuestIndex( battle_rules_str.c_str(), challenge_template_str.c_str()); } else { - this->quests_by_number.emplace(vq->quest_number, new Quest(vq)); + shared_ptr q(new Quest(vq)); + this->quests_by_number.emplace(vq->quest_number, q); + this->quests_by_category_id_and_number[q->category_id].emplace(vq->quest_number, q); static_game_data_log.info("(%s) Created %s %c quest %" PRIu32 " (%s) (%s, %s (%" PRIu32 "), %s)%s%s%s", basename.c_str(), name_for_enum(vq->version), @@ -710,47 +733,48 @@ shared_ptr QuestIndex::get(uint32_t quest_number) const { } } -const vector>& QuestIndex::categories( - QuestMenuType menu_type, Episode episode, Version version) const { +vector> QuestIndex::categories( + QuestMenuType menu_type, + Episode episode, + Version version, + function)> include_condition) const { // The episode filter should apply in normal or solo mode if ((menu_type != QuestMenuType::NORMAL) && (menu_type != QuestMenuType::SOLO)) { episode = Episode::NONE; } - uint64_t key = (static_cast(menu_type) << 20) | (static_cast(episode) << 16) | static_cast(version); - try { - return this->category_filter_results_cache.at(key); - } catch (const out_of_range&) { - auto& ret = this->category_filter_results_cache[key]; - for (const auto& cat : this->category_index->categories) { - if (cat->check_flag(menu_type) && !this->filter(menu_type, episode, version, cat->category_id).empty()) { - ret.emplace_back(cat); - } + vector> ret; + for (const auto& cat : this->category_index->categories) { + if (cat->check_flag(menu_type) && !this->filter(menu_type, episode, version, cat->category_id, include_condition, 1).empty()) { + ret.emplace_back(cat); } - return ret; } + return ret; } -const vector>& QuestIndex::filter( - QuestMenuType menu_type, Episode episode, Version version, uint32_t category_id) const { +vector> QuestIndex::filter( + QuestMenuType menu_type, + Episode episode, + Version version, + uint32_t category_id, + function)> include_condition, + size_t limit) const { if ((menu_type != QuestMenuType::NORMAL) && (menu_type != QuestMenuType::SOLO)) { episode = Episode::NONE; } - uint64_t key = (static_cast(episode) << 48) | (static_cast(version) << 32) | category_id; - try { - return this->quest_filter_results_cache.at(key); - } catch (const out_of_range&) { - vector>& ret = this->quest_filter_results_cache[key]; - for (auto it : this->quests_by_number) { - if (((episode == Episode::NONE) || (it.second->episode == episode)) && - (it.second->category_id == category_id) && - it.second->has_version_any_language(version)) { - ret.emplace_back(it.second); + vector> ret; + for (auto it : this->quests_by_category_id_and_number.at(category_id)) { + if (((episode == Episode::NONE) || (it.second->episode == episode)) && + it.second->has_version_any_language(version) && + (!include_condition || include_condition(it.second))) { + ret.emplace_back(it.second); + if (limit && (ret.size() >= limit)) { + break; } } - return ret; } + return ret; } string encode_download_quest_data(const string& compressed_data, size_t decompressed_size, uint32_t encryption_seed) { diff --git a/src/Quest.hh b/src/Quest.hh index 60f6eb2b..a989bdc6 100644 --- a/src/Quest.hh +++ b/src/Quest.hh @@ -11,6 +11,7 @@ #include "PlayerSubordinates.hh" #include "QuestScript.hh" #include "StaticGameData.hh" +#include "TeamIndex.hh" enum class QuestFileFormat { BIN_DAT = 0, @@ -69,6 +70,8 @@ struct VersionedQuest { std::shared_ptr pvr_contents; std::shared_ptr battle_rules; ssize_t challenge_template_index; + int16_t require_flag; // <0 = none + std::string require_team_reward_key; VersionedQuest( uint32_t quest_number, @@ -79,7 +82,9 @@ struct VersionedQuest { std::shared_ptr dat_contents, std::shared_ptr pvr_contents, std::shared_ptr battle_rules = nullptr, - ssize_t challenge_template_index = -1); + ssize_t challenge_template_index = -1, + int16_t require_flag = -1, + const std::string& require_team_reward_key = ""); std::string bin_filename() const; std::string dat_filename() const; @@ -112,26 +117,36 @@ public: std::string name; std::shared_ptr battle_rules; ssize_t challenge_template_index; + int16_t require_flag; + std::string require_team_reward_key; std::map> versions; }; struct QuestIndex { + using IncludeCondition = std::function)>; + std::string directory; std::shared_ptr category_index; std::map> quests_by_number; - - mutable std::unordered_map>> category_filter_results_cache; - mutable std::unordered_map>> quest_filter_results_cache; + std::map>> quests_by_category_id_and_number; QuestIndex(const std::string& directory, std::shared_ptr category_index, bool is_ep3); std::shared_ptr get(uint32_t quest_number) const; - const std::vector>& categories( - QuestMenuType menu_type, Episode episode, Version version) const; - const std::vector>& filter( - QuestMenuType menu_type, Episode episode, Version version, uint32_t category_id) const; + std::vector> categories( + QuestMenuType menu_type, + Episode episode, + Version version, + IncludeCondition include_condition = nullptr) const; + std::vector> filter( + QuestMenuType menu_type, + Episode episode, + Version version, + uint32_t category_id, + IncludeCondition include_condition = nullptr, + size_t limit = 0) const; }; std::string encode_download_quest_data( diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 0da2206e..9217bebd 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -2306,6 +2306,7 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { shared_ptr l = c->lobby.lock(); Episode episode = l ? l->episode : Episode::NONE; QuestMenuType menu_type = QuestMenuType::NORMAL; + QuestIndex::IncludeCondition include_condition = nullptr; if (!l) { // Assume the menu to be sent is the download quest menu if the client // is not in any lobby @@ -2324,9 +2325,12 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { break; } } + if (!(c->license->flags & License::Flag::DISABLE_QUEST_REQUIREMENTS)) { + include_condition = l->quest_include_condition(); + } } - const auto& quests = quest_index->filter(menu_type, episode, c->version(), item_id); + const auto& quests = quest_index->filter(menu_type, episode, c->version(), item_id, include_condition); send_quest_menu(c, MenuID::QUEST, quests, !l); break; } @@ -4145,10 +4149,13 @@ static void on_6F(shared_ptr c, uint16_t command, uint32_t, string& data } c->config.clear_flag(Client::Flag::LOADING); + send_server_time(c); if (l->base_version == Version::BB_V4) { send_set_exp_multiplier(l); } - send_server_time(c); + if (c->version() == Version::BB_V4) { + send_all_nearby_team_metadatas_to_client(c, false); + } if (c->config.check_flag(Client::Flag::DEBUG_ENABLED)) { string variations_str; diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index da3aa456..838c588f 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -691,9 +691,9 @@ static void on_set_player_visible(shared_ptr c, uint8_t command, uint8_t send_message_box(c, "$C6All lobbies are full.\n\n$C7You are in a private lobby. You can use the\nteleporter to join other lobbies if there is space\navailable."); send_lobby_message_box(c, ""); } - } - if (c->version() == Version::BB_V4) { - send_all_nearby_team_metadatas_to_client(c, false); + if (c->version() == Version::BB_V4) { + send_all_nearby_team_metadatas_to_client(c, false); + } } } } @@ -1741,12 +1741,10 @@ static void on_set_quest_flag(shared_ptr c, uint8_t command, uint8_t fla // The client explicitly checks for both 0 and 1 - any other value means no // operation is performed. - size_t byte_index = flag_index >> 3; - uint8_t mask = 0x80 >> (flag_index & 7); if (action == 0) { - p->quest_flags[difficulty][byte_index] |= mask; + p->quest_flags.set(difficulty, flag_index); } else if (action == 1) { - p->quest_flags[difficulty][byte_index] &= (~mask); + p->quest_flags.clear(difficulty, flag_index); } forward_subcommand(c, command, flag, data, size); diff --git a/src/SaveFileFormats.hh b/src/SaveFileFormats.hh index 2d4454c6..28f70b54 100644 --- a/src/SaveFileFormats.hh +++ b/src/SaveFileFormats.hh @@ -195,7 +195,7 @@ struct PSOBBCharacterFile { /* 04E8 */ le_uint32_t play_time_seconds = 0; /* 04EC */ le_uint32_t option_flags = 0x00040058; /* 04F0 */ parray unknown_a2; - /* 04F4 */ parray, 4> quest_flags; + /* 04F4 */ QuestFlags quest_flags; /* 06F4 */ le_uint32_t death_count = 0; /* 06F8 */ PlayerBank bank; /* 19C0 */ GuildCardBB guild_card; @@ -343,8 +343,7 @@ struct PSOGCCharacterFile { /* 0430:0014 */ be_uint32_t save_count; /* 0434:0018 */ parray unknown_a4; /* 0450:0034 */ parray unknown_a5; - // 1024 bits (flags) per difficulty - /* 0460:0044 */ parray, 4> quest_flags; + /* 0460:0044 */ QuestFlags quest_flags; /* 0660:0244 */ be_uint32_t death_count; /* 0664:0248 */ PlayerBank bank; /* 192C:1510 */ GuildCardGC guild_card; @@ -761,7 +760,7 @@ struct LegacySavedPlayerDataBB { // .nsc file format /* 185C */ pstring info_board; /* 19B4 */ PlayerInventory inventory; /* 1D00 */ parray unknown_a2; - /* 1D04 */ parray, 4> quest_flags; + /* 1D04 */ QuestFlags quest_flags; /* 1F04 */ le_uint32_t death_count; /* 1F08 */ parray quest_global_flags; /* 1F60 */ parray tech_menu_config; diff --git a/src/SendCommands.cc b/src/SendCommands.cc index d01cdbc6..ea446819 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -1440,8 +1440,14 @@ void send_quest_categories_menu_t( shared_ptr quest_index, QuestMenuType menu_type, Episode episode) { + QuestIndex::IncludeCondition include_condition = nullptr; + if (!(c->license->flags & License::Flag::DISABLE_QUEST_REQUIREMENTS)) { + auto l = c->lobby.lock(); + include_condition = l ? l->quest_include_condition() : nullptr; + } + vector entries; - for (const auto& cat : quest_index->categories(menu_type, episode, c->version())) { + for (const auto& cat : quest_index->categories(menu_type, episode, c->version(), include_condition)) { auto& e = entries.emplace_back(); e.menu_id = menu_id; e.item_id = cat->category_id; diff --git a/system/quests/battle/b88001.json b/system/quests/battle/b88001.json index d61b57ae..a6bcaf73 100644 --- a/system/quests/battle/b88001.json +++ b/system/quests/battle/b88001.json @@ -1,23 +1,48 @@ { - "battle_rules": { - "tech_disk_mode": "ALLOW", - "weapon_and_armor_mode": "ALLOW", - "mag_mode": "ALLOW", - "tool_mode": "ALLOW", - "trap_mode": "ALL_PLAYERS", - "respawn_mode": 0, - "replace_char": 0, - "drop_weapon": 1, - "is_teams": 1, - "hide_target_reticle": 1, - "death_level_up": 3, - "meseta_mode": "ALLOW", - "enable_sonar": 1, - "time_limit": 10, - "forbid_scape_dolls": 1, - "death_tech_level_up": 1, - "trap_counts": [5, 5, 5, 5], - "sonar_count": 5, - "box_drop_area": 10 - } + // Each quest may have an optional JSON file (like this one) that defines + // server-side behaviors for the quest. + + // For battle quests, the BattleRules field should be defined to match the + // rules that the quest defines internally. These are the rules for Battle 1. + "BattleRules": { + "TechDiskMode": "ALLOW", + "WeaponAndArmorMode": "ALLOW", + "MagMode": "ALLOW", + "ToolMode": "ALLOW", + "TrapMode": "ALL_PLAYERS", + "RespawnMode": 0, + "ReplaceChar": 0, + "DropWeapon": 1, + "IsTeams": 1, + "HideTargetReticle": 1, + "DeathLevelUp": 3, + "MesetaMode": "ALLOW", + "EnableSonar": 1, + "TimeLimit": 10, + "ForbidScapeDolls": 1, + "DeathTechLevelUp": 1, + "TrapCounts": [5, 5, 5, 5], + "SonarCount": 5, + "BoxDropArea": 10, + // These rules are used by other battles, but not by Battle 1: + // "Lives": 10, + // "MaxTechLevel": 15, + // "CharLevel": 1, + }, + + // Challenge quests should specify the ChallengeTemplateIndex field, which + // should match the template that the quest uses to replace player characters. + // "ChallengeTemplateIndex": 0, + + // Quests may be set to be unavailable until a preceding quest has been + // cleared. To enable this feature, set a value for RequireFlag in the quest's + // JSON file. This field is ignored if the player has the + // DISABLE_QUEST_REQUIREMENTS flag in their license. + // "RequireFlag": 0x01F5, + + // Quests on BB may be set to be available only through a team reward. To + // enable this feature, set a value for RequireTeamRewardKey in the quest's + // JSON file. This field is ignored if the player has the + // DISABLE_QUEST_REQUIREMENTS flag in their license. + // "RequireTeamRewardKey": "PointOfDisasterQuest", } diff --git a/system/quests/battle/b88002.json b/system/quests/battle/b88002.json index 5bd165d4..2f325969 100644 --- a/system/quests/battle/b88002.json +++ b/system/quests/battle/b88002.json @@ -1,25 +1,25 @@ { - "battle_rules": { - "tech_disk_mode": "LIMIT_LEVEL", - "weapon_and_armor_mode": "CLEAR_AND_ALLOW", - "mag_mode": "FORBID_ALL", - "tool_mode": "CLEAR_AND_ALLOW", - "trap_mode": "ALL_PLAYERS", - "respawn_mode": 0, - "replace_char": 1, - "drop_weapon": 1, - "is_teams": 0, - "hide_target_reticle": 1, - "death_level_up": 5, - "meseta_mode": "CLEAR_AND_ALLOW", - "enable_sonar": 1, - "max_tech_level": 0, - "char_level": 0, - "time_limit": 10, - "forbid_scape_dolls": 1, - "death_tech_level_up": 1, - "trap_counts": [0, 5, 5, 5], - "sonar_count": 5, - "box_drop_area": 1 + "BattleRules": { + "TechDiskMode": "LIMIT_LEVEL", + "WeaponAndArmorMode": "CLEAR_AND_ALLOW", + "MagMode": "FORBID_ALL", + "ToolMode": "CLEAR_AND_ALLOW", + "TrapMode": "ALL_PLAYERS", + "RespawnMode": 0, + "ReplaceChar": 1, + "DropWeapon": 1, + "IsTeams": 0, + "HideTargetReticle": 1, + "DeathLevelUp": 5, + "MesetaMode": "CLEAR_AND_ALLOW", + "EnableSonar": 1, + "MaxTechLevel": 0, + "CharLevel": 0, + "TimeLimit": 10, + "ForbidScapeDolls": 1, + "DeathTechLevelUp": 1, + "TrapCounts": [0, 5, 5, 5], + "SonarCount": 5, + "BoxDropArea": 1 } } diff --git a/system/quests/battle/b88003.json b/system/quests/battle/b88003.json index ba43e8f2..e9eb5b01 100644 --- a/system/quests/battle/b88003.json +++ b/system/quests/battle/b88003.json @@ -1,25 +1,25 @@ { - "battle_rules": { - "tech_disk_mode": "LIMIT_LEVEL", - "weapon_and_armor_mode": "CLEAR_AND_ALLOW", - "mag_mode": "FORBID_ALL", - "tool_mode": "CLEAR_AND_ALLOW", - "trap_mode": "ALL_PLAYERS", - "respawn_mode": 2, - "replace_char": 1, - "drop_weapon": 0, - "is_teams": 0, - "hide_target_reticle": 1, - "death_level_up": 3, - "meseta_mode": "FORBID_ALL", - "enable_sonar": 0, - "lives": 10, - "max_tech_level": 0, - "char_level": 4, - "time_limit": 10, - "forbid_scape_dolls": 1, - "death_tech_level_up": 1, - "trap_counts": [0, 10, 10, 10], - "box_drop_area": 3 + "BattleRules": { + "TechDiskMode": "LIMIT_LEVEL", + "WeaponAndArmorMode": "CLEAR_AND_ALLOW", + "MagMode": "FORBID_ALL", + "ToolMode": "CLEAR_AND_ALLOW", + "TrapMode": "ALL_PLAYERS", + "RespawnMode": 2, + "ReplaceChar": 1, + "DropWeapon": 0, + "IsTeams": 0, + "HideTargetReticle": 1, + "DeathLevelUp": 3, + "MesetaMode": "FORBID_ALL", + "EnableSonar": 0, + "Lives": 10, + "MaxTechLevel": 0, + "CharLevel": 4, + "TimeLimit": 10, + "ForbidScapeDolls": 1, + "DeathTechLevelUp": 1, + "TrapCounts": [0, 10, 10, 10], + "BoxDropArea": 3 } } diff --git a/system/quests/battle/b88004.json b/system/quests/battle/b88004.json index b4c5f3f8..145b0fce 100644 --- a/system/quests/battle/b88004.json +++ b/system/quests/battle/b88004.json @@ -1,26 +1,26 @@ { - "battle_rules": { - "tech_disk_mode": "LIMIT_LEVEL", - "weapon_and_armor_mode": "CLEAR_AND_ALLOW", - "mag_mode": "FORBID_ALL", - "tool_mode": "CLEAR_AND_ALLOW", - "trap_mode": "ALL_PLAYERS", - "respawn_mode": 2, - "replace_char": 1, - "drop_weapon": 1, - "is_teams": 0, - "hide_target_reticle": 1, - "death_level_up": 5, - "meseta_mode": "CLEAR_AND_ALLOW", - "enable_sonar": 1, - "lives": 10, - "max_tech_level": 1, - "char_level": 1, - "time_limit": 10, - "forbid_scape_dolls": 1, - "death_tech_level_up": 1, - "trap_counts": [5, 5, 5, 5], - "sonar_count": 5, - "box_drop_area": 1 + "BattleRules": { + "TechDiskMode": "LIMIT_LEVEL", + "WeaponAndArmorMode": "CLEAR_AND_ALLOW", + "MagMode": "FORBID_ALL", + "ToolMode": "CLEAR_AND_ALLOW", + "TrapMode": "ALL_PLAYERS", + "RespawnMode": 2, + "ReplaceChar": 1, + "DropWeapon": 1, + "IsTeams": 0, + "HideTargetReticle": 1, + "DeathLevelUp": 5, + "MesetaMode": "CLEAR_AND_ALLOW", + "EnableSonar": 1, + "Lives": 10, + "MaxTechLevel": 1, + "CharLevel": 1, + "TimeLimit": 10, + "ForbidScapeDolls": 1, + "DeathTechLevelUp": 1, + "TrapCounts": [5, 5, 5, 5], + "SonarCount": 5, + "BoxDropArea": 1 } } diff --git a/system/quests/battle/b88005.json b/system/quests/battle/b88005.json index 1a1b7e4f..eb27be15 100644 --- a/system/quests/battle/b88005.json +++ b/system/quests/battle/b88005.json @@ -1,23 +1,23 @@ { - "battle_rules": { - "tech_disk_mode": "ALLOW", - "weapon_and_armor_mode": "ALLOW", - "mag_mode": "FORBID_ALL", - "tool_mode": "ALLOW", - "trap_mode": "ALL_PLAYERS", - "respawn_mode": 1, - "replace_char": 0, - "drop_weapon": 1, - "is_teams": 1, - "hide_target_reticle": 1, - "death_level_up": 5, - "meseta_mode": "CLEAR_AND_ALLOW", - "enable_sonar": 1, - "time_limit": 10, - "forbid_scape_dolls": 1, - "death_tech_level_up": 1, - "trap_counts": [5, 5, 5, 5], - "sonar_count": 5, - "box_drop_area": 10 + "BattleRules": { + "TechDiskMode": "ALLOW", + "WeaponAndArmorMode": "ALLOW", + "MagMode": "FORBID_ALL", + "ToolMode": "ALLOW", + "TrapMode": "ALL_PLAYERS", + "RespawnMode": 1, + "ReplaceChar": 0, + "DropWeapon": 1, + "IsTeams": 1, + "HideTargetReticle": 1, + "DeathLevelUp": 5, + "MesetaMode": "CLEAR_AND_ALLOW", + "EnableSonar": 1, + "TimeLimit": 10, + "ForbidScapeDolls": 1, + "DeathTechLevelUp": 1, + "TrapCounts": [5, 5, 5, 5], + "SonarCount": 5, + "BoxDropArea": 10 } } diff --git a/system/quests/battle/b88006.json b/system/quests/battle/b88006.json index fed2a172..2efd38ed 100644 --- a/system/quests/battle/b88006.json +++ b/system/quests/battle/b88006.json @@ -1,25 +1,25 @@ { - "battle_rules": { - "tech_disk_mode": "LIMIT_LEVEL", - "weapon_and_armor_mode": "CLEAR_AND_ALLOW", - "mag_mode": "FORBID_ALL", - "tool_mode": "CLEAR_AND_ALLOW", - "trap_mode": "ALL_PLAYERS", - "respawn_mode": 2, - "replace_char": 1, - "drop_weapon": 1, - "is_teams": 1, - "hide_target_reticle": 1, - "death_level_up": 3, - "meseta_mode": "CLEAR_AND_ALLOW", - "enable_sonar": 0, - "lives": 10, - "max_tech_level": 4, - "char_level": 19, - "time_limit": 10, - "forbid_scape_dolls": 1, - "death_tech_level_up": 1, - "trap_counts": [5, 5, 0, 0], - "box_drop_area": 6 + "BattleRules": { + "TechDiskMode": "LIMIT_LEVEL", + "WeaponAndArmorMode": "CLEAR_AND_ALLOW", + "MagMode": "FORBID_ALL", + "ToolMode": "CLEAR_AND_ALLOW", + "TrapMode": "ALL_PLAYERS", + "RespawnMode": 2, + "ReplaceChar": 1, + "DropWeapon": 1, + "IsTeams": 1, + "HideTargetReticle": 1, + "DeathLevelUp": 3, + "MesetaMode": "CLEAR_AND_ALLOW", + "EnableSonar": 0, + "Lives": 10, + "MaxTechLevel": 4, + "CharLevel": 19, + "TimeLimit": 10, + "ForbidScapeDolls": 1, + "DeathTechLevelUp": 1, + "TrapCounts": [5, 5, 0, 0], + "BoxDropArea": 6 } } diff --git a/system/quests/battle/b88007.json b/system/quests/battle/b88007.json index dacad0c5..ee2473df 100644 --- a/system/quests/battle/b88007.json +++ b/system/quests/battle/b88007.json @@ -1,26 +1,26 @@ { - "battle_rules": { - "tech_disk_mode": "LIMIT_LEVEL", - "weapon_and_armor_mode": "CLEAR_AND_ALLOW", - "mag_mode": "FORBID_ALL", - "tool_mode": "CLEAR_AND_ALLOW", - "trap_mode": "ALL_PLAYERS", - "respawn_mode": 2, - "replace_char": 1, - "drop_weapon": 1, - "is_teams": 0, - "hide_target_reticle": 1, - "death_level_up": 1, - "meseta_mode": "CLEAR_AND_ALLOW", - "enable_sonar": 1, - "lives": 15, - "max_tech_level": 0, - "char_level": 0, - "time_limit": 10, - "forbid_scape_dolls": 0, - "death_tech_level_up": 0, - "trap_counts": [0, 0, 1, 0], - "sonar_count": 10, - "box_drop_area": 2 + "BattleRules": { + "TechDiskMode": "LIMIT_LEVEL", + "WeaponAndArmorMode": "CLEAR_AND_ALLOW", + "MagMode": "FORBID_ALL", + "ToolMode": "CLEAR_AND_ALLOW", + "TrapMode": "ALL_PLAYERS", + "RespawnMode": 2, + "ReplaceChar": 1, + "DropWeapon": 1, + "IsTeams": 0, + "HideTargetReticle": 1, + "DeathLevelUp": 1, + "MesetaMode": "CLEAR_AND_ALLOW", + "EnableSonar": 1, + "Lives": 15, + "MaxTechLevel": 0, + "CharLevel": 0, + "TimeLimit": 10, + "ForbidScapeDolls": 0, + "DeathTechLevelUp": 0, + "TrapCounts": [0, 0, 1, 0], + "SonarCount": 10, + "BoxDropArea": 2 } } diff --git a/system/quests/battle/b88008.json b/system/quests/battle/b88008.json index d15c186a..07cc2881 100644 --- a/system/quests/battle/b88008.json +++ b/system/quests/battle/b88008.json @@ -1,26 +1,26 @@ { - "battle_rules": { - "tech_disk_mode": "LIMIT_LEVEL", - "weapon_and_armor_mode": "CLEAR_AND_ALLOW", - "mag_mode": "FORBID_ALL", - "tool_mode": "CLEAR_AND_ALLOW", - "trap_mode": "ALL_PLAYERS", - "respawn_mode": 2, - "replace_char": 1, - "drop_weapon": 0, - "is_teams": 0, - "hide_target_reticle": 1, - "death_level_up": 5, - "meseta_mode": "FORBID_ALL", - "enable_sonar": 1, - "lives": 10, - "max_tech_level": 0, - "char_level": 19, - "time_limit": 10, - "forbid_scape_dolls": 1, - "death_tech_level_up": 0, - "trap_counts": [0, 10, 10, 10], - "sonar_count": 10, - "box_drop_area": 1 + "BattleRules": { + "TechDiskMode": "LIMIT_LEVEL", + "WeaponAndArmorMode": "CLEAR_AND_ALLOW", + "MagMode": "FORBID_ALL", + "ToolMode": "CLEAR_AND_ALLOW", + "TrapMode": "ALL_PLAYERS", + "RespawnMode": 2, + "ReplaceChar": 1, + "DropWeapon": 0, + "IsTeams": 0, + "HideTargetReticle": 1, + "DeathLevelUp": 5, + "MesetaMode": "FORBID_ALL", + "EnableSonar": 1, + "Lives": 10, + "MaxTechLevel": 0, + "CharLevel": 19, + "TimeLimit": 10, + "ForbidScapeDolls": 1, + "DeathTechLevelUp": 0, + "TrapCounts": [0, 10, 10, 10], + "SonarCount": 10, + "BoxDropArea": 1 } } diff --git a/system/quests/challenge-ep1/c88101.json b/system/quests/challenge-ep1/c88101.json index f196012f..72399d77 100644 --- a/system/quests/challenge-ep1/c88101.json +++ b/system/quests/challenge-ep1/c88101.json @@ -1,3 +1,3 @@ { - "challenge_template_index": 0 + "ChallengeTemplateIndex": 0 } diff --git a/system/quests/challenge-ep1/c88102.json b/system/quests/challenge-ep1/c88102.json index 7ea02225..2faae4ca 100644 --- a/system/quests/challenge-ep1/c88102.json +++ b/system/quests/challenge-ep1/c88102.json @@ -1,3 +1,3 @@ { - "challenge_template_index": 1 + "ChallengeTemplateIndex": 1 } diff --git a/system/quests/challenge-ep1/c88103.json b/system/quests/challenge-ep1/c88103.json index 261da30a..c9833bf5 100644 --- a/system/quests/challenge-ep1/c88103.json +++ b/system/quests/challenge-ep1/c88103.json @@ -1,3 +1,3 @@ { - "challenge_template_index": 2 + "ChallengeTemplateIndex": 2 } diff --git a/system/quests/challenge-ep1/c88104.json b/system/quests/challenge-ep1/c88104.json index c9024661..5fa3f804 100644 --- a/system/quests/challenge-ep1/c88104.json +++ b/system/quests/challenge-ep1/c88104.json @@ -1,3 +1,3 @@ { - "challenge_template_index": 3 + "ChallengeTemplateIndex": 3 } diff --git a/system/quests/challenge-ep1/c88105.json b/system/quests/challenge-ep1/c88105.json index 42795483..45a7511d 100644 --- a/system/quests/challenge-ep1/c88105.json +++ b/system/quests/challenge-ep1/c88105.json @@ -1,3 +1,3 @@ { - "challenge_template_index": 4 + "ChallengeTemplateIndex": 4 } diff --git a/system/quests/challenge-ep1/c88106.json b/system/quests/challenge-ep1/c88106.json index 83e8ff50..bfb434cf 100644 --- a/system/quests/challenge-ep1/c88106.json +++ b/system/quests/challenge-ep1/c88106.json @@ -1,3 +1,3 @@ { - "challenge_template_index": 5 + "ChallengeTemplateIndex": 5 } diff --git a/system/quests/challenge-ep1/c88107.json b/system/quests/challenge-ep1/c88107.json index b6d3ba32..30b0532f 100644 --- a/system/quests/challenge-ep1/c88107.json +++ b/system/quests/challenge-ep1/c88107.json @@ -1,3 +1,3 @@ { - "challenge_template_index": 6 + "ChallengeTemplateIndex": 6 } diff --git a/system/quests/challenge-ep1/c88108.json b/system/quests/challenge-ep1/c88108.json index 40925e01..5b750df8 100644 --- a/system/quests/challenge-ep1/c88108.json +++ b/system/quests/challenge-ep1/c88108.json @@ -1,3 +1,3 @@ { - "challenge_template_index": 7 + "ChallengeTemplateIndex": 7 } diff --git a/system/quests/challenge-ep1/c88109.json b/system/quests/challenge-ep1/c88109.json index c1bc8eeb..e7f35692 100644 --- a/system/quests/challenge-ep1/c88109.json +++ b/system/quests/challenge-ep1/c88109.json @@ -1,3 +1,3 @@ { - "challenge_template_index": 8 + "ChallengeTemplateIndex": 8 } diff --git a/system/quests/challenge-ep2/d88201.json b/system/quests/challenge-ep2/d88201.json index 7ea02225..2faae4ca 100644 --- a/system/quests/challenge-ep2/d88201.json +++ b/system/quests/challenge-ep2/d88201.json @@ -1,3 +1,3 @@ { - "challenge_template_index": 1 + "ChallengeTemplateIndex": 1 } diff --git a/system/quests/challenge-ep2/d88202.json b/system/quests/challenge-ep2/d88202.json index 42795483..45a7511d 100644 --- a/system/quests/challenge-ep2/d88202.json +++ b/system/quests/challenge-ep2/d88202.json @@ -1,3 +1,3 @@ { - "challenge_template_index": 4 + "ChallengeTemplateIndex": 4 } diff --git a/system/quests/challenge-ep2/d88203.json b/system/quests/challenge-ep2/d88203.json index 83e8ff50..bfb434cf 100644 --- a/system/quests/challenge-ep2/d88203.json +++ b/system/quests/challenge-ep2/d88203.json @@ -1,3 +1,3 @@ { - "challenge_template_index": 5 + "ChallengeTemplateIndex": 5 } diff --git a/system/quests/challenge-ep2/d88204.json b/system/quests/challenge-ep2/d88204.json index c1bc8eeb..e7f35692 100644 --- a/system/quests/challenge-ep2/d88204.json +++ b/system/quests/challenge-ep2/d88204.json @@ -1,3 +1,3 @@ { - "challenge_template_index": 8 + "ChallengeTemplateIndex": 8 } diff --git a/system/quests/challenge-ep2/d88205.json b/system/quests/challenge-ep2/d88205.json index c1bc8eeb..e7f35692 100644 --- a/system/quests/challenge-ep2/d88205.json +++ b/system/quests/challenge-ep2/d88205.json @@ -1,3 +1,3 @@ { - "challenge_template_index": 8 + "ChallengeTemplateIndex": 8 } diff --git a/system/quests/government-ep1/q402-bb.json b/system/quests/government-ep1/q402-bb.json new file mode 100644 index 00000000..b848b5dc --- /dev/null +++ b/system/quests/government-ep1/q402-bb.json @@ -0,0 +1,3 @@ +{ + "RequireFlag": 0x01F5, +} diff --git a/system/quests/government-ep1/q403-bb.json b/system/quests/government-ep1/q403-bb.json new file mode 100644 index 00000000..b874455a --- /dev/null +++ b/system/quests/government-ep1/q403-bb.json @@ -0,0 +1,3 @@ +{ + "RequireFlag": 0x01F7, +} diff --git a/system/quests/government-ep1/q404-bb.json b/system/quests/government-ep1/q404-bb.json new file mode 100644 index 00000000..6f9fcceb --- /dev/null +++ b/system/quests/government-ep1/q404-bb.json @@ -0,0 +1,3 @@ +{ + "RequireFlag": 0x01F9, +} diff --git a/system/quests/government-ep1/q405-bb.json b/system/quests/government-ep1/q405-bb.json new file mode 100644 index 00000000..e4771e85 --- /dev/null +++ b/system/quests/government-ep1/q405-bb.json @@ -0,0 +1,3 @@ +{ + "RequireFlag": 0x01FB, +} diff --git a/system/quests/government-ep1/q406-bb.json b/system/quests/government-ep1/q406-bb.json new file mode 100644 index 00000000..60c10f94 --- /dev/null +++ b/system/quests/government-ep1/q406-bb.json @@ -0,0 +1,3 @@ +{ + "RequireFlag": 0x01FD, +} diff --git a/system/quests/government-ep1/q407-bb.json b/system/quests/government-ep1/q407-bb.json new file mode 100644 index 00000000..d9fbd98a --- /dev/null +++ b/system/quests/government-ep1/q407-bb.json @@ -0,0 +1,3 @@ +{ + "RequireFlag": 0x01FF, +} diff --git a/system/quests/government-ep1/q408-bb.json b/system/quests/government-ep1/q408-bb.json new file mode 100644 index 00000000..369e31d5 --- /dev/null +++ b/system/quests/government-ep1/q408-bb.json @@ -0,0 +1,3 @@ +{ + "RequireFlag": 0x0201, +} diff --git a/system/quests/government-ep1/q409-bb.json b/system/quests/government-ep1/q409-bb.json new file mode 100644 index 00000000..cd64b00a --- /dev/null +++ b/system/quests/government-ep1/q409-bb.json @@ -0,0 +1,3 @@ +{ + "RequireFlag": 0x0203, +} diff --git a/system/quests/government-ep1/q410-bb.json b/system/quests/government-ep1/q410-bb.json new file mode 100644 index 00000000..4c85c9dc --- /dev/null +++ b/system/quests/government-ep1/q410-bb.json @@ -0,0 +1,3 @@ +{ + "RequireFlag": 0x0205, +} diff --git a/system/quests/government-ep1/q411-bb.json b/system/quests/government-ep1/q411-bb.json new file mode 100644 index 00000000..94ced7db --- /dev/null +++ b/system/quests/government-ep1/q411-bb.json @@ -0,0 +1,3 @@ +{ + "RequireFlag": 0x0207, +} diff --git a/system/quests/government-ep1/q412-bb.json b/system/quests/government-ep1/q412-bb.json new file mode 100644 index 00000000..d17ccce3 --- /dev/null +++ b/system/quests/government-ep1/q412-bb.json @@ -0,0 +1,3 @@ +{ + "RequireFlag": 0x0209, +} diff --git a/system/quests/government-ep1/q413-bb.json b/system/quests/government-ep1/q413-bb.json new file mode 100644 index 00000000..00acf966 --- /dev/null +++ b/system/quests/government-ep1/q413-bb.json @@ -0,0 +1,3 @@ +{ + "RequireFlag": 0x020B, +} diff --git a/system/quests/government-ep1/q414-bb.json b/system/quests/government-ep1/q414-bb.json new file mode 100644 index 00000000..2e4bb7c5 --- /dev/null +++ b/system/quests/government-ep1/q414-bb.json @@ -0,0 +1,3 @@ +{ + "RequireFlag": 0x020D, +} diff --git a/system/quests/government-ep1/q415-bb.json b/system/quests/government-ep1/q415-bb.json new file mode 100644 index 00000000..69f27b95 --- /dev/null +++ b/system/quests/government-ep1/q415-bb.json @@ -0,0 +1,3 @@ +{ + "RequireFlag": 0x020F, +} diff --git a/system/quests/government-ep2/q452-bb.json b/system/quests/government-ep2/q452-bb.json new file mode 100644 index 00000000..168385dd --- /dev/null +++ b/system/quests/government-ep2/q452-bb.json @@ -0,0 +1,3 @@ +{ + "RequireFlag": 0x0213, +} diff --git a/system/quests/government-ep2/q453-bb.json b/system/quests/government-ep2/q453-bb.json new file mode 100644 index 00000000..ee889e48 --- /dev/null +++ b/system/quests/government-ep2/q453-bb.json @@ -0,0 +1,3 @@ +{ + "RequireFlag": 0x0215, +} diff --git a/system/quests/government-ep2/q454-bb.json b/system/quests/government-ep2/q454-bb.json new file mode 100644 index 00000000..91c74c87 --- /dev/null +++ b/system/quests/government-ep2/q454-bb.json @@ -0,0 +1,3 @@ +{ + "RequireFlag": 0x0217, +} diff --git a/system/quests/government-ep2/q455-bb.json b/system/quests/government-ep2/q455-bb.json new file mode 100644 index 00000000..51f3357e --- /dev/null +++ b/system/quests/government-ep2/q455-bb.json @@ -0,0 +1,3 @@ +{ + "RequireFlag": 0x0219, +} diff --git a/system/quests/government-ep2/q456-bb.json b/system/quests/government-ep2/q456-bb.json new file mode 100644 index 00000000..eaa90b9e --- /dev/null +++ b/system/quests/government-ep2/q456-bb.json @@ -0,0 +1,3 @@ +{ + "RequireFlag": 0x021B, +} diff --git a/system/quests/government-ep2/q457-bb.json b/system/quests/government-ep2/q457-bb.json new file mode 100644 index 00000000..1ad36d36 --- /dev/null +++ b/system/quests/government-ep2/q457-bb.json @@ -0,0 +1,3 @@ +{ + "RequireFlag": 0x021D, +} diff --git a/system/quests/government-ep2/q458-bb.json b/system/quests/government-ep2/q458-bb.json new file mode 100644 index 00000000..14a797af --- /dev/null +++ b/system/quests/government-ep2/q458-bb.json @@ -0,0 +1,3 @@ +{ + "RequireFlag": 0x021F, +} diff --git a/system/quests/government-ep2/q459-bb.json b/system/quests/government-ep2/q459-bb.json new file mode 100644 index 00000000..d6f43d31 --- /dev/null +++ b/system/quests/government-ep2/q459-bb.json @@ -0,0 +1,3 @@ +{ + "RequireFlag": 0x0221, +} diff --git a/system/quests/government-ep2/q460-bb.json b/system/quests/government-ep2/q460-bb.json new file mode 100644 index 00000000..f3316531 --- /dev/null +++ b/system/quests/government-ep2/q460-bb.json @@ -0,0 +1,3 @@ +{ + "RequireFlag": 0x0223, +} diff --git a/system/quests/government-ep2/q461-bb.json b/system/quests/government-ep2/q461-bb.json new file mode 100644 index 00000000..260c270c --- /dev/null +++ b/system/quests/government-ep2/q461-bb.json @@ -0,0 +1,3 @@ +{ + "RequireFlag": 0x0225, +} diff --git a/system/quests/government-ep2/q462-bb.json b/system/quests/government-ep2/q462-bb.json new file mode 100644 index 00000000..df994729 --- /dev/null +++ b/system/quests/government-ep2/q462-bb.json @@ -0,0 +1,3 @@ +{ + "RequireFlag": 0x0227, +} diff --git a/system/quests/government-ep2/q463-bb.json b/system/quests/government-ep2/q463-bb.json new file mode 100644 index 00000000..1342cb90 --- /dev/null +++ b/system/quests/government-ep2/q463-bb.json @@ -0,0 +1,3 @@ +{ + "RequireFlag": 0x0229, +} diff --git a/system/quests/government-ep2/q464-bb.json b/system/quests/government-ep2/q464-bb.json new file mode 100644 index 00000000..0e67aa65 --- /dev/null +++ b/system/quests/government-ep2/q464-bb.json @@ -0,0 +1,3 @@ +{ + "RequireFlag": 0x022B, +} diff --git a/system/quests/government-ep2/q465-bb.json b/system/quests/government-ep2/q465-bb.json new file mode 100644 index 00000000..32c79778 --- /dev/null +++ b/system/quests/government-ep2/q465-bb.json @@ -0,0 +1,3 @@ +{ + "RequireFlag": 0x022D, +} diff --git a/system/quests/government-ep2/q466-bb.json b/system/quests/government-ep2/q466-bb.json new file mode 100644 index 00000000..80674aaf --- /dev/null +++ b/system/quests/government-ep2/q466-bb.json @@ -0,0 +1,3 @@ +{ + "RequireFlag": 0x022F, +} diff --git a/system/quests/government-ep2/q467-bb.json b/system/quests/government-ep2/q467-bb.json new file mode 100644 index 00000000..e36165ec --- /dev/null +++ b/system/quests/government-ep2/q467-bb.json @@ -0,0 +1,3 @@ +{ + "RequireFlag": 0x0231, +} diff --git a/system/quests/government-ep2/q468-bb.json b/system/quests/government-ep2/q468-bb.json new file mode 100644 index 00000000..b3a3a217 --- /dev/null +++ b/system/quests/government-ep2/q468-bb.json @@ -0,0 +1,3 @@ +{ + "RequireFlag": 0x0233, +} diff --git a/system/quests/government-ep4/q702-bb.json b/system/quests/government-ep4/q702-bb.json new file mode 100644 index 00000000..6a2c4e06 --- /dev/null +++ b/system/quests/government-ep4/q702-bb.json @@ -0,0 +1,3 @@ +{ + "RequireFlag": 0x02BD, +} diff --git a/system/quests/government-ep4/q703-bb.json b/system/quests/government-ep4/q703-bb.json new file mode 100644 index 00000000..69f1bc5b --- /dev/null +++ b/system/quests/government-ep4/q703-bb.json @@ -0,0 +1,3 @@ +{ + "RequireFlag": 0x02BE, +} diff --git a/system/quests/government-ep4/q704-bb.json b/system/quests/government-ep4/q704-bb.json new file mode 100644 index 00000000..fa5b0652 --- /dev/null +++ b/system/quests/government-ep4/q704-bb.json @@ -0,0 +1,3 @@ +{ + "RequireFlag": 0x02BF, +} diff --git a/system/quests/government-ep4/q705-bb.json b/system/quests/government-ep4/q705-bb.json new file mode 100644 index 00000000..ec0157f0 --- /dev/null +++ b/system/quests/government-ep4/q705-bb.json @@ -0,0 +1,3 @@ +{ + "RequireFlag": 0x02C0, +} diff --git a/system/quests/government-ep4/q706-bb.json b/system/quests/government-ep4/q706-bb.json new file mode 100644 index 00000000..b3758669 --- /dev/null +++ b/system/quests/government-ep4/q706-bb.json @@ -0,0 +1,3 @@ +{ + "RequireFlag": 0x02C1, +} diff --git a/system/quests/government-ep4/q707-bb.json b/system/quests/government-ep4/q707-bb.json new file mode 100644 index 00000000..8cccbc50 --- /dev/null +++ b/system/quests/government-ep4/q707-bb.json @@ -0,0 +1,3 @@ +{ + "RequireFlag": 0x02C2, +} diff --git a/system/quests/government-ep4/q708-bb.json b/system/quests/government-ep4/q708-bb.json new file mode 100644 index 00000000..a887c33f --- /dev/null +++ b/system/quests/government-ep4/q708-bb.json @@ -0,0 +1,3 @@ +{ + "RequireFlag": 0x02C3, +} diff --git a/system/quests/team/q709.json b/system/quests/team/q709.json new file mode 100644 index 00000000..3bb493ec --- /dev/null +++ b/system/quests/team/q709.json @@ -0,0 +1,3 @@ +{ + "RequireTeamRewardKey": "PointOfDisasterQuest", +} diff --git a/system/quests/team/q710.json b/system/quests/team/q710.json new file mode 100644 index 00000000..9dae8fa2 --- /dev/null +++ b/system/quests/team/q710.json @@ -0,0 +1,3 @@ +{ + "RequireTeamRewardKey": "TheRobotsReckoningQuest", +}