implement quest unlock flags
This commit is contained in:
@@ -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.
|
||||
|
||||
|
||||
@@ -4626,7 +4626,7 @@ struct G_SyncFlagState_6x6E_Decompressed {
|
||||
|
||||
struct G_SetQuestFlags_6x6F {
|
||||
G_UnusedHeader header;
|
||||
parray<parray<uint8_t, 0x80>, 4> quest_flags_by_difficulty;
|
||||
QuestFlags quest_flags;
|
||||
} __packed__;
|
||||
|
||||
// 6x70: Sync player disp data and inventory (used while loading into game)
|
||||
|
||||
+15
-14
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -401,3 +401,30 @@ unordered_map<uint32_t, shared_ptr<Client>> Lobby::clients_by_serial_number() co
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
QuestIndex::IncludeCondition Lobby::quest_include_condition() const {
|
||||
return [this](shared_ptr<const Quest> 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;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -192,6 +192,8 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
|
||||
void on_item_id_generated_externally(uint32_t item_id);
|
||||
void assign_inventory_item_ids(std::shared_ptr<Client> c);
|
||||
|
||||
QuestIndex::IncludeCondition quest_include_condition() const;
|
||||
|
||||
static uint8_t game_event_for_lobby_event(uint8_t lobby_event);
|
||||
|
||||
std::unordered_map<uint32_t, std::shared_ptr<Client>> clients_by_serial_number() const;
|
||||
|
||||
+46
-46
@@ -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()},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -483,6 +483,40 @@ inline PlayerDispDataBB convert_player_disp_data<PlayerDispDataBB>(
|
||||
return src;
|
||||
}
|
||||
|
||||
struct QuestFlagsForDifficulty {
|
||||
parray<uint8_t, 0x80> 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<QuestFlagsForDifficulty, 4> 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,
|
||||
|
||||
+56
-32
@@ -204,7 +204,9 @@ VersionedQuest::VersionedQuest(
|
||||
std::shared_ptr<const std::string> dat_contents,
|
||||
std::shared_ptr<const std::string> pvr_contents,
|
||||
std::shared_ptr<const BattleRules> 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<const VersionedQuest> 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<const VersionedQuest> 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<BattleRules> 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<Quest> 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<const Quest> QuestIndex::get(uint32_t quest_number) const {
|
||||
}
|
||||
}
|
||||
|
||||
const vector<shared_ptr<const QuestCategoryIndex::Category>>& QuestIndex::categories(
|
||||
QuestMenuType menu_type, Episode episode, Version version) const {
|
||||
vector<shared_ptr<const QuestCategoryIndex::Category>> QuestIndex::categories(
|
||||
QuestMenuType menu_type,
|
||||
Episode episode,
|
||||
Version version,
|
||||
function<bool(std::shared_ptr<const Quest>)> 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<uint32_t>(menu_type) << 20) | (static_cast<uint32_t>(episode) << 16) | static_cast<uint32_t>(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<shared_ptr<const QuestCategoryIndex::Category>> 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<shared_ptr<const Quest>>& QuestIndex::filter(
|
||||
QuestMenuType menu_type, Episode episode, Version version, uint32_t category_id) const {
|
||||
vector<shared_ptr<const Quest>> QuestIndex::filter(
|
||||
QuestMenuType menu_type,
|
||||
Episode episode,
|
||||
Version version,
|
||||
uint32_t category_id,
|
||||
function<bool(std::shared_ptr<const Quest>)> include_condition,
|
||||
size_t limit) const {
|
||||
if ((menu_type != QuestMenuType::NORMAL) && (menu_type != QuestMenuType::SOLO)) {
|
||||
episode = Episode::NONE;
|
||||
}
|
||||
|
||||
uint64_t key = (static_cast<uint64_t>(episode) << 48) | (static_cast<uint64_t>(version) << 32) | category_id;
|
||||
try {
|
||||
return this->quest_filter_results_cache.at(key);
|
||||
} catch (const out_of_range&) {
|
||||
vector<shared_ptr<const Quest>>& 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<shared_ptr<const Quest>> 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) {
|
||||
|
||||
+23
-8
@@ -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<const std::string> pvr_contents;
|
||||
std::shared_ptr<const BattleRules> 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<const std::string> dat_contents,
|
||||
std::shared_ptr<const std::string> pvr_contents,
|
||||
std::shared_ptr<const BattleRules> 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<const BattleRules> battle_rules;
|
||||
ssize_t challenge_template_index;
|
||||
int16_t require_flag;
|
||||
std::string require_team_reward_key;
|
||||
std::map<uint32_t, std::shared_ptr<const VersionedQuest>> versions;
|
||||
};
|
||||
|
||||
struct QuestIndex {
|
||||
using IncludeCondition = std::function<bool(std::shared_ptr<const Quest>)>;
|
||||
|
||||
std::string directory;
|
||||
std::shared_ptr<const QuestCategoryIndex> category_index;
|
||||
|
||||
std::map<uint32_t, std::shared_ptr<Quest>> quests_by_number;
|
||||
|
||||
mutable std::unordered_map<uint32_t, std::vector<std::shared_ptr<const QuestCategoryIndex::Category>>> category_filter_results_cache;
|
||||
mutable std::unordered_map<uint64_t, std::vector<std::shared_ptr<const Quest>>> quest_filter_results_cache;
|
||||
std::map<uint32_t, std::map<uint32_t, std::shared_ptr<Quest>>> quests_by_category_id_and_number;
|
||||
|
||||
QuestIndex(const std::string& directory, std::shared_ptr<const QuestCategoryIndex> category_index, bool is_ep3);
|
||||
|
||||
std::shared_ptr<const Quest> get(uint32_t quest_number) const;
|
||||
|
||||
const std::vector<std::shared_ptr<const QuestCategoryIndex::Category>>& categories(
|
||||
QuestMenuType menu_type, Episode episode, Version version) const;
|
||||
const std::vector<std::shared_ptr<const Quest>>& filter(
|
||||
QuestMenuType menu_type, Episode episode, Version version, uint32_t category_id) const;
|
||||
std::vector<std::shared_ptr<const QuestCategoryIndex::Category>> categories(
|
||||
QuestMenuType menu_type,
|
||||
Episode episode,
|
||||
Version version,
|
||||
IncludeCondition include_condition = nullptr) const;
|
||||
std::vector<std::shared_ptr<const Quest>> 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(
|
||||
|
||||
@@ -2306,6 +2306,7 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
||||
shared_ptr<Lobby> 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<Client> 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<Client> 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;
|
||||
|
||||
@@ -691,9 +691,9 @@ static void on_set_player_visible(shared_ptr<Client> 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<Client> 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);
|
||||
|
||||
@@ -195,7 +195,7 @@ struct PSOBBCharacterFile {
|
||||
/* 04E8 */ le_uint32_t play_time_seconds = 0;
|
||||
/* 04EC */ le_uint32_t option_flags = 0x00040058;
|
||||
/* 04F0 */ parray<uint8_t, 4> unknown_a2;
|
||||
/* 04F4 */ parray<parray<uint8_t, 0x80>, 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<uint8_t, 0x1C> unknown_a4;
|
||||
/* 0450:0034 */ parray<uint8_t, 0x10> unknown_a5;
|
||||
// 1024 bits (flags) per difficulty
|
||||
/* 0460:0044 */ parray<parray<uint8_t, 0x80>, 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<TextEncoding::UTF16, 0x00AC> info_board;
|
||||
/* 19B4 */ PlayerInventory inventory;
|
||||
/* 1D00 */ parray<uint8_t, 4> unknown_a2;
|
||||
/* 1D04 */ parray<parray<uint8_t, 0x80>, 4> quest_flags;
|
||||
/* 1D04 */ QuestFlags quest_flags;
|
||||
/* 1F04 */ le_uint32_t death_count;
|
||||
/* 1F08 */ parray<le_uint32_t, 0x0016> quest_global_flags;
|
||||
/* 1F60 */ parray<le_uint16_t, 0x0014> tech_menu_config;
|
||||
|
||||
+7
-1
@@ -1440,8 +1440,14 @@ void send_quest_categories_menu_t(
|
||||
shared_ptr<const QuestIndex> 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<EntryT> 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;
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"challenge_template_index": 0
|
||||
"ChallengeTemplateIndex": 0
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"challenge_template_index": 1
|
||||
"ChallengeTemplateIndex": 1
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"challenge_template_index": 2
|
||||
"ChallengeTemplateIndex": 2
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"challenge_template_index": 3
|
||||
"ChallengeTemplateIndex": 3
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"challenge_template_index": 4
|
||||
"ChallengeTemplateIndex": 4
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"challenge_template_index": 5
|
||||
"ChallengeTemplateIndex": 5
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"challenge_template_index": 6
|
||||
"ChallengeTemplateIndex": 6
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"challenge_template_index": 7
|
||||
"ChallengeTemplateIndex": 7
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"challenge_template_index": 8
|
||||
"ChallengeTemplateIndex": 8
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"challenge_template_index": 1
|
||||
"ChallengeTemplateIndex": 1
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"challenge_template_index": 4
|
||||
"ChallengeTemplateIndex": 4
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"challenge_template_index": 5
|
||||
"ChallengeTemplateIndex": 5
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"challenge_template_index": 8
|
||||
"ChallengeTemplateIndex": 8
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"challenge_template_index": 8
|
||||
"ChallengeTemplateIndex": 8
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireFlag": 0x01F5,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireFlag": 0x01F7,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireFlag": 0x01F9,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireFlag": 0x01FB,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireFlag": 0x01FD,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireFlag": 0x01FF,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireFlag": 0x0201,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireFlag": 0x0203,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireFlag": 0x0205,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireFlag": 0x0207,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireFlag": 0x0209,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireFlag": 0x020B,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireFlag": 0x020D,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireFlag": 0x020F,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireFlag": 0x0213,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireFlag": 0x0215,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireFlag": 0x0217,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireFlag": 0x0219,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireFlag": 0x021B,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireFlag": 0x021D,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireFlag": 0x021F,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireFlag": 0x0221,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireFlag": 0x0223,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireFlag": 0x0225,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireFlag": 0x0227,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireFlag": 0x0229,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireFlag": 0x022B,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireFlag": 0x022D,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireFlag": 0x022F,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireFlag": 0x0231,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireFlag": 0x0233,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireFlag": 0x02BD,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireFlag": 0x02BE,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireFlag": 0x02BF,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireFlag": 0x02C0,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireFlag": 0x02C1,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireFlag": 0x02C2,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireFlag": 0x02C3,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireTeamRewardKey": "PointOfDisasterQuest",
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"RequireTeamRewardKey": "TheRobotsReckoningQuest",
|
||||
}
|
||||
Reference in New Issue
Block a user