implement solo quest progression flags
This commit is contained in:
@@ -94,6 +94,7 @@ set(SOURCES
|
|||||||
src/PSOGCObjectGraph.cc
|
src/PSOGCObjectGraph.cc
|
||||||
src/PSOProtocol.cc
|
src/PSOProtocol.cc
|
||||||
src/Quest.cc
|
src/Quest.cc
|
||||||
|
src/QuestAvailabilityExpression.cc
|
||||||
src/QuestScript.cc
|
src/QuestScript.cc
|
||||||
src/RareItemSet.cc
|
src/RareItemSet.cc
|
||||||
src/ReceiveCommands.cc
|
src/ReceiveCommands.cc
|
||||||
|
|||||||
+19
-10
@@ -295,21 +295,30 @@ shared_ptr<const TeamIndex::Team> Client::team() const {
|
|||||||
return team;
|
return team;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Client::can_access_quest(shared_ptr<const Quest> q, uint8_t difficulty) const {
|
bool Client::can_see_quest(shared_ptr<const Quest> q, uint8_t difficulty) const {
|
||||||
if (this->license && (this->license->flags & License::Flag::DISABLE_QUEST_REQUIREMENTS)) {
|
if (this->license && (this->license->flags & License::Flag::DISABLE_QUEST_REQUIREMENTS)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if ((q->require_flag >= 0) &&
|
if (!q->available_expression) {
|
||||||
!this->character()->quest_flags.get(difficulty, q->require_flag)) {
|
return true;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
if (!q->require_team_reward_key.empty()) {
|
string expr = q->available_expression->str();
|
||||||
auto team = this->team();
|
bool ret = q->available_expression->evaluate(this->character()->quest_flags.data.at(difficulty), this->team());
|
||||||
if (!team || !team->has_reward(q->require_team_reward_key)) {
|
this->log.info("Evaluating quest availability expression %s => %s", expr.c_str(), ret ? "TRUE" : "FALSE");
|
||||||
return false;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Client::can_play_quest(shared_ptr<const Quest> q, uint8_t difficulty) const {
|
||||||
|
if (this->license && (this->license->flags & License::Flag::DISABLE_QUEST_REQUIREMENTS)) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return true;
|
if (!q->enabled_expression) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
string expr = q->enabled_expression->str();
|
||||||
|
bool ret = q->enabled_expression->evaluate(this->character()->quest_flags.data.at(difficulty), this->team());
|
||||||
|
this->log.info("Evaluating quest enabled expression %s => %s", expr.c_str(), ret ? "TRUE" : "FALSE");
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::dispatch_save_game_data(evutil_socket_t, short, void* ctx) {
|
void Client::dispatch_save_game_data(evutil_socket_t, short, void* ctx) {
|
||||||
|
|||||||
+2
-1
@@ -260,7 +260,8 @@ public:
|
|||||||
|
|
||||||
std::shared_ptr<const TeamIndex::Team> team() const;
|
std::shared_ptr<const TeamIndex::Team> team() const;
|
||||||
|
|
||||||
bool can_access_quest(std::shared_ptr<const Quest> q, uint8_t difficulty) const;
|
bool can_see_quest(std::shared_ptr<const Quest> q, uint8_t difficulty) const;
|
||||||
|
bool can_play_quest(std::shared_ptr<const Quest> q, uint8_t difficulty) const;
|
||||||
|
|
||||||
static void dispatch_save_game_data(evutil_socket_t, short, void* ctx);
|
static void dispatch_save_game_data(evutil_socket_t, short, void* ctx);
|
||||||
void save_game_data();
|
void save_game_data();
|
||||||
|
|||||||
+20
-1
@@ -1996,6 +1996,15 @@ struct C_ChangeShipOrBlock_A0_A1 {
|
|||||||
|
|
||||||
template <TextEncoding Encoding, size_t ShortDescLength>
|
template <TextEncoding Encoding, size_t ShortDescLength>
|
||||||
struct S_QuestMenuEntry {
|
struct S_QuestMenuEntry {
|
||||||
|
// Note: The game treats menu_id as two 8-bit fields followed by a 16-bit
|
||||||
|
// field. In most situations, this is opaque to the server, so we treat it as
|
||||||
|
// a single 32-bit field, but in the case of the quest menu, the second byte
|
||||||
|
// is used to determine the icon that appears to the left of the quest name.
|
||||||
|
// Specifically:
|
||||||
|
// 0 = online quest icon (green diamond)
|
||||||
|
// 1 = download quest icon (green square with outlined diamond)
|
||||||
|
// 2 = completed download quest icon (orange square with outlined diamond)
|
||||||
|
// Anything else = same as 1
|
||||||
le_uint32_t menu_id = 0;
|
le_uint32_t menu_id = 0;
|
||||||
le_uint32_t item_id = 0;
|
le_uint32_t item_id = 0;
|
||||||
pstring<Encoding, 0x20> name;
|
pstring<Encoding, 0x20> name;
|
||||||
@@ -2007,7 +2016,17 @@ struct S_QuestMenuEntry_DC_GC_A2_A4 : S_QuestMenuEntry<TextEncoding::MARKED, 0x7
|
|||||||
} __packed__;
|
} __packed__;
|
||||||
struct S_QuestMenuEntry_XB_A2_A4 : S_QuestMenuEntry<TextEncoding::MARKED, 0x80> {
|
struct S_QuestMenuEntry_XB_A2_A4 : S_QuestMenuEntry<TextEncoding::MARKED, 0x80> {
|
||||||
} __packed__;
|
} __packed__;
|
||||||
struct S_QuestMenuEntry_BB_A2_A4 : S_QuestMenuEntry<TextEncoding::UTF16, 0x7A> {
|
|
||||||
|
struct S_QuestMenuEntry_BB_A2_A4 {
|
||||||
|
le_uint32_t menu_id = 0;
|
||||||
|
le_uint32_t item_id = 0;
|
||||||
|
pstring<TextEncoding::UTF16, 0x20> name;
|
||||||
|
pstring<TextEncoding::UTF16, 0x78> short_description;
|
||||||
|
// If this field is set, a yellow hex icon is displayed instead of the green
|
||||||
|
// or orange diamond icon, and the quest is grayed out and cannot be selected.
|
||||||
|
// This field is ignored if the icon type (see S_QuestMenuEntry) isn't 1 or 2.
|
||||||
|
uint8_t disabled = 0;
|
||||||
|
parray<uint8_t, 3> unused;
|
||||||
} __packed__;
|
} __packed__;
|
||||||
|
|
||||||
// A3 (S->C): Quest information
|
// A3 (S->C): Quest information
|
||||||
|
|||||||
+8
-4
@@ -410,12 +410,16 @@ unordered_map<uint32_t, shared_ptr<Client>> Lobby::clients_by_serial_number() co
|
|||||||
}
|
}
|
||||||
|
|
||||||
QuestIndex::IncludeCondition Lobby::quest_include_condition() const {
|
QuestIndex::IncludeCondition Lobby::quest_include_condition() const {
|
||||||
return [this](shared_ptr<const Quest> q) -> bool {
|
return [this](shared_ptr<const Quest> q) -> QuestIndex::IncludeState {
|
||||||
|
bool is_enabled = true;
|
||||||
for (const auto& lc : this->clients) {
|
for (const auto& lc : this->clients) {
|
||||||
if (lc && !lc->can_access_quest(q, this->difficulty)) {
|
if (lc && !lc->can_see_quest(q, this->difficulty)) {
|
||||||
return false;
|
return QuestIndex::IncludeState::HIDDEN;
|
||||||
|
}
|
||||||
|
if (lc && !lc->can_play_quest(q, this->difficulty)) {
|
||||||
|
is_enabled = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return is_enabled ? QuestIndex::IncludeState::AVAILABLE : QuestIndex::IncludeState::DISABLED;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -17,8 +17,8 @@ constexpr uint32_t MAIN = 0x11000011;
|
|||||||
constexpr uint32_t INFORMATION = 0x22000022;
|
constexpr uint32_t INFORMATION = 0x22000022;
|
||||||
constexpr uint32_t LOBBY = 0x33000033;
|
constexpr uint32_t LOBBY = 0x33000033;
|
||||||
constexpr uint32_t GAME = 0x44000044;
|
constexpr uint32_t GAME = 0x44000044;
|
||||||
constexpr uint32_t QUEST = 0x55000055;
|
constexpr uint32_t QUEST = 0x55010155;
|
||||||
constexpr uint32_t QUEST_CATEGORIES = 0x66000066;
|
constexpr uint32_t QUEST_CATEGORIES = 0x66010166;
|
||||||
constexpr uint32_t PROXY_DESTINATIONS = 0x77000077;
|
constexpr uint32_t PROXY_DESTINATIONS = 0x77000077;
|
||||||
constexpr uint32_t PROGRAMS = 0x88000088;
|
constexpr uint32_t PROGRAMS = 0x88000088;
|
||||||
constexpr uint32_t PATCHES = 0x99000099;
|
constexpr uint32_t PATCHES = 0x99000099;
|
||||||
|
|||||||
+35
-23
@@ -205,8 +205,8 @@ VersionedQuest::VersionedQuest(
|
|||||||
std::shared_ptr<const std::string> pvr_contents,
|
std::shared_ptr<const std::string> pvr_contents,
|
||||||
std::shared_ptr<const BattleRules> battle_rules,
|
std::shared_ptr<const BattleRules> battle_rules,
|
||||||
ssize_t challenge_template_index,
|
ssize_t challenge_template_index,
|
||||||
int16_t require_flag,
|
std::shared_ptr<const QuestAvailabilityExpression> available_expression,
|
||||||
const string& require_team_reward_key)
|
std::shared_ptr<const QuestAvailabilityExpression> enabled_expression)
|
||||||
: quest_number(quest_number),
|
: quest_number(quest_number),
|
||||||
category_id(category_id),
|
category_id(category_id),
|
||||||
episode(Episode::NONE),
|
episode(Episode::NONE),
|
||||||
@@ -219,8 +219,8 @@ VersionedQuest::VersionedQuest(
|
|||||||
pvr_contents(pvr_contents),
|
pvr_contents(pvr_contents),
|
||||||
battle_rules(battle_rules),
|
battle_rules(battle_rules),
|
||||||
challenge_template_index(challenge_template_index),
|
challenge_template_index(challenge_template_index),
|
||||||
require_flag(require_flag),
|
available_expression(available_expression),
|
||||||
require_team_reward_key(require_team_reward_key) {
|
enabled_expression(enabled_expression) {
|
||||||
|
|
||||||
auto bin_decompressed = prs_decompress(*this->bin_contents);
|
auto bin_decompressed = prs_decompress(*this->bin_contents);
|
||||||
|
|
||||||
@@ -378,8 +378,8 @@ Quest::Quest(shared_ptr<const VersionedQuest> initial_version)
|
|||||||
name(initial_version->name),
|
name(initial_version->name),
|
||||||
battle_rules(initial_version->battle_rules),
|
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),
|
available_expression(initial_version->available_expression),
|
||||||
require_team_reward_key(initial_version->require_team_reward_key) {
|
enabled_expression(initial_version->enabled_expression) {
|
||||||
this->versions.emplace(this->versions_key(initial_version->version, initial_version->language), initial_version);
|
this->versions.emplace(this->versions_key(initial_version->version, initial_version->language), initial_version);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -409,11 +409,17 @@ void Quest::add_version(shared_ptr<const VersionedQuest> vq) {
|
|||||||
if (this->challenge_template_index != vq->challenge_template_index) {
|
if (this->challenge_template_index != vq->challenge_template_index) {
|
||||||
throw runtime_error("quest version has different challenge template index");
|
throw runtime_error("quest version has different challenge template index");
|
||||||
}
|
}
|
||||||
if (this->require_flag != vq->require_flag) {
|
if (!this->available_expression != !vq->available_expression) {
|
||||||
throw runtime_error("quest version has different required flag");
|
throw runtime_error("quest version has available expression but root quest does not, or vice versa");
|
||||||
}
|
}
|
||||||
if (this->require_team_reward_key != vq->require_team_reward_key) {
|
if (this->available_expression && *this->available_expression != *vq->available_expression) {
|
||||||
throw runtime_error("quest version has different required team reward key");
|
throw runtime_error("quest version has a different available expression");
|
||||||
|
}
|
||||||
|
if (!this->enabled_expression != !vq->enabled_expression) {
|
||||||
|
throw runtime_error("quest version has enabled expression but root quest does not, or vice versa");
|
||||||
|
}
|
||||||
|
if (this->enabled_expression && *this->enabled_expression != *vq->enabled_expression) {
|
||||||
|
throw runtime_error("quest version has a different enabled expression");
|
||||||
}
|
}
|
||||||
|
|
||||||
this->versions.emplace(this->versions_key(vq->version, vq->language), vq);
|
this->versions.emplace(this->versions_key(vq->version, vq->language), vq);
|
||||||
@@ -639,8 +645,8 @@ QuestIndex::QuestIndex(
|
|||||||
JSON metadata_json = nullptr;
|
JSON metadata_json = nullptr;
|
||||||
shared_ptr<BattleRules> battle_rules;
|
shared_ptr<BattleRules> battle_rules;
|
||||||
ssize_t challenge_template_index = -1;
|
ssize_t challenge_template_index = -1;
|
||||||
int16_t require_flag = -1;
|
shared_ptr<const QuestAvailabilityExpression> available_expression;
|
||||||
string require_team_reward_key;
|
shared_ptr<const QuestAvailabilityExpression> enabled_expression;
|
||||||
try {
|
try {
|
||||||
json_filename = basename;
|
json_filename = basename;
|
||||||
metadata_json = JSON::parse(*json_files.at(json_filename));
|
metadata_json = JSON::parse(*json_files.at(json_filename));
|
||||||
@@ -665,9 +671,12 @@ QuestIndex::QuestIndex(
|
|||||||
challenge_template_index = metadata_json.at("ChallengeTemplateIndex").as_int();
|
challenge_template_index = metadata_json.at("ChallengeTemplateIndex").as_int();
|
||||||
} catch (const out_of_range&) {
|
} catch (const out_of_range&) {
|
||||||
}
|
}
|
||||||
require_flag = metadata_json.get_int("RequireFlag", -1);
|
|
||||||
try {
|
try {
|
||||||
require_team_reward_key = metadata_json.get_string("RequireTeamRewardKey");
|
available_expression = make_shared<QuestAvailabilityExpression>(metadata_json.get_string("AvailableIf"));
|
||||||
|
} catch (const out_of_range&) {
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
enabled_expression = make_shared<QuestAvailabilityExpression>(metadata_json.get_string("EnabledIf"));
|
||||||
} catch (const out_of_range&) {
|
} catch (const out_of_range&) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -682,8 +691,8 @@ QuestIndex::QuestIndex(
|
|||||||
pvr_contents,
|
pvr_contents,
|
||||||
battle_rules,
|
battle_rules,
|
||||||
challenge_template_index,
|
challenge_template_index,
|
||||||
require_flag,
|
available_expression,
|
||||||
require_team_reward_key);
|
enabled_expression);
|
||||||
|
|
||||||
auto category_name = this->category_index->at(vq->category_id)->name;
|
auto category_name = this->category_index->at(vq->category_id)->name;
|
||||||
string dat_str = dat_filename.empty() ? "" : (" with layout from " + dat_filename + ".dat");
|
string dat_str = dat_filename.empty() ? "" : (" with layout from " + dat_filename + ".dat");
|
||||||
@@ -737,7 +746,7 @@ vector<shared_ptr<const QuestCategoryIndex::Category>> QuestIndex::categories(
|
|||||||
QuestMenuType menu_type,
|
QuestMenuType menu_type,
|
||||||
Episode episode,
|
Episode episode,
|
||||||
Version version,
|
Version version,
|
||||||
function<bool(std::shared_ptr<const Quest>)> include_condition) const {
|
IncludeCondition include_condition) const {
|
||||||
// The episode filter should apply in normal or solo mode
|
// The episode filter should apply in normal or solo mode
|
||||||
if ((menu_type != QuestMenuType::NORMAL) && (menu_type != QuestMenuType::SOLO)) {
|
if ((menu_type != QuestMenuType::NORMAL) && (menu_type != QuestMenuType::SOLO)) {
|
||||||
episode = Episode::NONE;
|
episode = Episode::NONE;
|
||||||
@@ -752,27 +761,30 @@ vector<shared_ptr<const QuestCategoryIndex::Category>> QuestIndex::categories(
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<shared_ptr<const Quest>> QuestIndex::filter(
|
vector<pair<QuestIndex::IncludeState, shared_ptr<const Quest>>> QuestIndex::filter(
|
||||||
QuestMenuType menu_type,
|
QuestMenuType menu_type,
|
||||||
Episode episode,
|
Episode episode,
|
||||||
Version version,
|
Version version,
|
||||||
uint32_t category_id,
|
uint32_t category_id,
|
||||||
function<bool(std::shared_ptr<const Quest>)> include_condition,
|
IncludeCondition include_condition,
|
||||||
size_t limit) const {
|
size_t limit) const {
|
||||||
if ((menu_type != QuestMenuType::NORMAL) && (menu_type != QuestMenuType::SOLO)) {
|
if ((menu_type != QuestMenuType::NORMAL) && (menu_type != QuestMenuType::SOLO)) {
|
||||||
episode = Episode::NONE;
|
episode = Episode::NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
vector<shared_ptr<const Quest>> ret;
|
vector<pair<IncludeState, shared_ptr<const Quest>>> ret;
|
||||||
auto category_it = this->quests_by_category_id_and_number.find(category_id);
|
auto category_it = this->quests_by_category_id_and_number.find(category_id);
|
||||||
if (category_it == this->quests_by_category_id_and_number.end()) {
|
if (category_it == this->quests_by_category_id_and_number.end()) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
for (auto it : category_it->second) {
|
for (auto it : category_it->second) {
|
||||||
if (((episode == Episode::NONE) || (it.second->episode == episode)) &&
|
if (((episode == Episode::NONE) || (it.second->episode == episode)) &&
|
||||||
it.second->has_version_any_language(version) &&
|
it.second->has_version_any_language(version)) {
|
||||||
(!include_condition || include_condition(it.second))) {
|
IncludeState state = include_condition ? include_condition(it.second) : IncludeState::AVAILABLE;
|
||||||
ret.emplace_back(it.second);
|
if (state == IncludeState::HIDDEN) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ret.emplace_back(make_pair(state, it.second));
|
||||||
if (limit && (ret.size() >= limit)) {
|
if (limit && (ret.size() >= limit)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-8
@@ -9,6 +9,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "PlayerSubordinates.hh"
|
#include "PlayerSubordinates.hh"
|
||||||
|
#include "QuestAvailabilityExpression.hh"
|
||||||
#include "QuestScript.hh"
|
#include "QuestScript.hh"
|
||||||
#include "StaticGameData.hh"
|
#include "StaticGameData.hh"
|
||||||
#include "TeamIndex.hh"
|
#include "TeamIndex.hh"
|
||||||
@@ -70,8 +71,8 @@ struct VersionedQuest {
|
|||||||
std::shared_ptr<const std::string> pvr_contents;
|
std::shared_ptr<const std::string> pvr_contents;
|
||||||
std::shared_ptr<const BattleRules> battle_rules;
|
std::shared_ptr<const BattleRules> battle_rules;
|
||||||
ssize_t challenge_template_index;
|
ssize_t challenge_template_index;
|
||||||
int16_t require_flag; // <0 = none
|
std::shared_ptr<const QuestAvailabilityExpression> available_expression;
|
||||||
std::string require_team_reward_key;
|
std::shared_ptr<const QuestAvailabilityExpression> enabled_expression;
|
||||||
|
|
||||||
VersionedQuest(
|
VersionedQuest(
|
||||||
uint32_t quest_number,
|
uint32_t quest_number,
|
||||||
@@ -83,8 +84,8 @@ struct VersionedQuest {
|
|||||||
std::shared_ptr<const std::string> pvr_contents,
|
std::shared_ptr<const std::string> pvr_contents,
|
||||||
std::shared_ptr<const BattleRules> battle_rules = nullptr,
|
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,
|
std::shared_ptr<const QuestAvailabilityExpression> available_expression = nullptr,
|
||||||
const std::string& require_team_reward_key = "");
|
std::shared_ptr<const QuestAvailabilityExpression> enabled_expression = nullptr);
|
||||||
|
|
||||||
std::string bin_filename() const;
|
std::string bin_filename() const;
|
||||||
std::string dat_filename() const;
|
std::string dat_filename() const;
|
||||||
@@ -117,13 +118,18 @@ public:
|
|||||||
std::string name;
|
std::string name;
|
||||||
std::shared_ptr<const BattleRules> battle_rules;
|
std::shared_ptr<const BattleRules> battle_rules;
|
||||||
ssize_t challenge_template_index;
|
ssize_t challenge_template_index;
|
||||||
int16_t require_flag;
|
std::shared_ptr<const QuestAvailabilityExpression> available_expression;
|
||||||
std::string require_team_reward_key;
|
std::shared_ptr<const QuestAvailabilityExpression> enabled_expression;
|
||||||
std::map<uint32_t, std::shared_ptr<const VersionedQuest>> versions;
|
std::map<uint32_t, std::shared_ptr<const VersionedQuest>> versions;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct QuestIndex {
|
struct QuestIndex {
|
||||||
using IncludeCondition = std::function<bool(std::shared_ptr<const Quest>)>;
|
enum class IncludeState {
|
||||||
|
HIDDEN = 0,
|
||||||
|
AVAILABLE,
|
||||||
|
DISABLED,
|
||||||
|
};
|
||||||
|
using IncludeCondition = std::function<IncludeState(std::shared_ptr<const Quest>)>;
|
||||||
|
|
||||||
std::string directory;
|
std::string directory;
|
||||||
std::shared_ptr<const QuestCategoryIndex> category_index;
|
std::shared_ptr<const QuestCategoryIndex> category_index;
|
||||||
@@ -140,7 +146,7 @@ struct QuestIndex {
|
|||||||
Episode episode,
|
Episode episode,
|
||||||
Version version,
|
Version version,
|
||||||
IncludeCondition include_condition = nullptr) const;
|
IncludeCondition include_condition = nullptr) const;
|
||||||
std::vector<std::shared_ptr<const Quest>> filter(
|
std::vector<std::pair<QuestIndex::IncludeState, std::shared_ptr<const Quest>>> filter(
|
||||||
QuestMenuType menu_type,
|
QuestMenuType menu_type,
|
||||||
Episode episode,
|
Episode episode,
|
||||||
Version version,
|
Version version,
|
||||||
|
|||||||
@@ -0,0 +1,248 @@
|
|||||||
|
#include "QuestAvailabilityExpression.hh"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <mutex>
|
||||||
|
#include <phosg/Encoding.hh>
|
||||||
|
#include <phosg/Filesystem.hh>
|
||||||
|
#include <phosg/Hash.hh>
|
||||||
|
#include <phosg/Random.hh>
|
||||||
|
#include <phosg/Strings.hh>
|
||||||
|
#include <phosg/Tools.hh>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "CommandFormats.hh"
|
||||||
|
#include "Compression.hh"
|
||||||
|
#include "Loggers.hh"
|
||||||
|
#include "PSOEncryption.hh"
|
||||||
|
#include "QuestScript.hh"
|
||||||
|
#include "SaveFileFormats.hh"
|
||||||
|
#include "Text.hh"
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
QuestAvailabilityExpression::QuestAvailabilityExpression(const string& text)
|
||||||
|
: root(this->parse_expr(text)) {}
|
||||||
|
|
||||||
|
QuestAvailabilityExpression::OrNode::OrNode(unique_ptr<const Node>&& left, unique_ptr<const Node>&& right)
|
||||||
|
: left(std::move(left)),
|
||||||
|
right(std::move(right)) {}
|
||||||
|
|
||||||
|
bool QuestAvailabilityExpression::OrNode::operator==(const Node& other) const {
|
||||||
|
try {
|
||||||
|
const OrNode& other_or = dynamic_cast<const OrNode&>(other);
|
||||||
|
return *other_or.left == *this->left && *other_or.right == *this->right;
|
||||||
|
} catch (const bad_cast&) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QuestAvailabilityExpression::OrNode::evaluate(
|
||||||
|
const QuestFlagsForDifficulty& flags, shared_ptr<const TeamIndex::Team> team) const {
|
||||||
|
return this->left->evaluate(flags, team) || this->right->evaluate(flags, team);
|
||||||
|
}
|
||||||
|
|
||||||
|
string QuestAvailabilityExpression::OrNode::str() const {
|
||||||
|
return "(" + this->left->str() + ") || (" + this->right->str() + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
QuestAvailabilityExpression::AndNode::AndNode(unique_ptr<const Node>&& left, unique_ptr<const Node>&& right)
|
||||||
|
: left(std::move(left)),
|
||||||
|
right(std::move(right)) {}
|
||||||
|
|
||||||
|
bool QuestAvailabilityExpression::AndNode::operator==(const Node& other) const {
|
||||||
|
try {
|
||||||
|
const AndNode& other_and = dynamic_cast<const AndNode&>(other);
|
||||||
|
return *other_and.left == *this->left && *other_and.right == *this->right;
|
||||||
|
} catch (const bad_cast&) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QuestAvailabilityExpression::AndNode::evaluate(
|
||||||
|
const QuestFlagsForDifficulty& flags, shared_ptr<const TeamIndex::Team> team) const {
|
||||||
|
return this->left->evaluate(flags, team) && this->right->evaluate(flags, team);
|
||||||
|
}
|
||||||
|
|
||||||
|
string QuestAvailabilityExpression::AndNode::str() const {
|
||||||
|
return "(" + this->left->str() + ") && (" + this->right->str() + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
QuestAvailabilityExpression::NotNode::NotNode(unique_ptr<const Node>&& sub)
|
||||||
|
: sub(std::move(sub)) {}
|
||||||
|
|
||||||
|
bool QuestAvailabilityExpression::NotNode::operator==(const Node& other) const {
|
||||||
|
try {
|
||||||
|
const NotNode& other_not = dynamic_cast<const NotNode&>(other);
|
||||||
|
return *other_not.sub == *this->sub;
|
||||||
|
} catch (const bad_cast&) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QuestAvailabilityExpression::NotNode::evaluate(
|
||||||
|
const QuestFlagsForDifficulty& flags, shared_ptr<const TeamIndex::Team> team) const {
|
||||||
|
return !this->sub->evaluate(flags, team);
|
||||||
|
}
|
||||||
|
|
||||||
|
string QuestAvailabilityExpression::NotNode::str() const {
|
||||||
|
return "!(" + this->sub->str() + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
QuestAvailabilityExpression::FlagLookupNode::FlagLookupNode(uint16_t flag_index)
|
||||||
|
: flag_index(flag_index) {}
|
||||||
|
|
||||||
|
bool QuestAvailabilityExpression::FlagLookupNode::operator==(const Node& other) const {
|
||||||
|
try {
|
||||||
|
const FlagLookupNode& other_flag = dynamic_cast<const FlagLookupNode&>(other);
|
||||||
|
return other_flag.flag_index == this->flag_index;
|
||||||
|
} catch (const bad_cast&) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QuestAvailabilityExpression::FlagLookupNode::evaluate(
|
||||||
|
const QuestFlagsForDifficulty& flags, shared_ptr<const TeamIndex::Team>) const {
|
||||||
|
return flags.get(this->flag_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
string QuestAvailabilityExpression::FlagLookupNode::str() const {
|
||||||
|
return string_printf("F_%04hX", this->flag_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
QuestAvailabilityExpression::TeamRewardLookupNode::TeamRewardLookupNode(const string& reward_name)
|
||||||
|
: reward_name(reward_name) {}
|
||||||
|
|
||||||
|
bool QuestAvailabilityExpression::TeamRewardLookupNode::operator==(const Node& other) const {
|
||||||
|
try {
|
||||||
|
const TeamRewardLookupNode& other_team_reward = dynamic_cast<const TeamRewardLookupNode&>(other);
|
||||||
|
return other_team_reward.reward_name == this->reward_name;
|
||||||
|
} catch (const bad_cast&) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QuestAvailabilityExpression::TeamRewardLookupNode::evaluate(
|
||||||
|
const QuestFlagsForDifficulty&, shared_ptr<const TeamIndex::Team> team) const {
|
||||||
|
return team && team->has_reward(this->reward_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
string QuestAvailabilityExpression::TeamRewardLookupNode::str() const {
|
||||||
|
return "T_" + this->reward_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
QuestAvailabilityExpression::ConstantNode::ConstantNode(bool value)
|
||||||
|
: value(value) {}
|
||||||
|
|
||||||
|
bool QuestAvailabilityExpression::ConstantNode::operator==(const Node& other) const {
|
||||||
|
try {
|
||||||
|
const ConstantNode& other_const = dynamic_cast<const ConstantNode&>(other);
|
||||||
|
return other_const.value == this->value;
|
||||||
|
} catch (const bad_cast&) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QuestAvailabilityExpression::ConstantNode::evaluate(
|
||||||
|
const QuestFlagsForDifficulty&, shared_ptr<const TeamIndex::Team>) const {
|
||||||
|
return this->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
string QuestAvailabilityExpression::ConstantNode::str() const {
|
||||||
|
return this->value ? "true" : "false";
|
||||||
|
}
|
||||||
|
|
||||||
|
unique_ptr<const QuestAvailabilityExpression::Node> QuestAvailabilityExpression::parse_expr(string_view text) {
|
||||||
|
// Strip off spaces and fully-enclosing parentheses
|
||||||
|
for (;;) {
|
||||||
|
size_t starting_size = text.size();
|
||||||
|
while (text.at(0) == ' ') {
|
||||||
|
text = text.substr(1);
|
||||||
|
}
|
||||||
|
while (text.at(text.size() - 1) == ' ') {
|
||||||
|
text = text.substr(0, text.size() - 1);
|
||||||
|
}
|
||||||
|
if (text.at(0) == '(' && text.at(text.size() - 1) == ')') {
|
||||||
|
// It doesn't suffice to just check the first ant last characters, since
|
||||||
|
// text could be like "(a) && (b)". Instead, we ignore the first and last
|
||||||
|
// characters, and don't strip anything if the internal parentheses are
|
||||||
|
// unbalanced.
|
||||||
|
size_t paren_level = 1;
|
||||||
|
for (size_t z = 1; z < text.size() - 1; z++) {
|
||||||
|
if (text[z] == '(') {
|
||||||
|
paren_level++;
|
||||||
|
} else if (text[z] == ')') {
|
||||||
|
paren_level--;
|
||||||
|
if (paren_level == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (paren_level > 0) {
|
||||||
|
text = text.substr(1, text.size() - 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (text.size() == starting_size) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for binary operators at the root level
|
||||||
|
size_t paren_level = 0;
|
||||||
|
size_t and_pos = 0;
|
||||||
|
size_t or_pos = 0;
|
||||||
|
for (size_t z = 0; z < text.size() - 1; z++) {
|
||||||
|
if (text[z] == '(') {
|
||||||
|
paren_level++;
|
||||||
|
} else if (text[z] == ')') {
|
||||||
|
paren_level--;
|
||||||
|
} else if ((text[z] == '&') && (text[z + 1] == '&') && !paren_level) {
|
||||||
|
and_pos = z;
|
||||||
|
} else if ((text[z] == '|') && (text[z + 1] == '|') && !paren_level) {
|
||||||
|
or_pos = z;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((or_pos && (!and_pos || (and_pos > or_pos)))) {
|
||||||
|
auto left = QuestAvailabilityExpression::parse_expr(text.substr(0, or_pos));
|
||||||
|
auto right = QuestAvailabilityExpression::parse_expr(text.substr(or_pos + 2));
|
||||||
|
return make_unique<OrNode>(std::move(left), std::move(right));
|
||||||
|
}
|
||||||
|
if ((and_pos && (!or_pos || (or_pos > and_pos)))) {
|
||||||
|
auto left = QuestAvailabilityExpression::parse_expr(text.substr(0, and_pos));
|
||||||
|
auto right = QuestAvailabilityExpression::parse_expr(text.substr(and_pos + 2));
|
||||||
|
return make_unique<AndNode>(std::move(left), std::move(right));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for not operator
|
||||||
|
if (text.at(0) == '!') {
|
||||||
|
auto sub = QuestAvailabilityExpression::parse_expr(text.substr(1));
|
||||||
|
return make_unique<NotNode>(std::move(sub));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for constants
|
||||||
|
if (text == "true") {
|
||||||
|
return make_unique<ConstantNode>(true);
|
||||||
|
}
|
||||||
|
if (text == "false") {
|
||||||
|
return make_unique<ConstantNode>(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for flag lookups
|
||||||
|
if (text.starts_with("F_")) {
|
||||||
|
char* endptr = nullptr;
|
||||||
|
uint64_t flag = strtoul(text.data() + 2, &endptr, 16);
|
||||||
|
if (endptr != text.data() + text.size()) {
|
||||||
|
throw runtime_error("invalid flag lookup token");
|
||||||
|
}
|
||||||
|
if (flag >= 0x400) {
|
||||||
|
throw runtime_error("invalid flag index");
|
||||||
|
}
|
||||||
|
return make_unique<FlagLookupNode>(flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text.starts_with("T_")) {
|
||||||
|
return make_unique<TeamRewardLookupNode>(string(text.substr(2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
throw runtime_error("unparseable expression");
|
||||||
|
}
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "PlayerSubordinates.hh"
|
||||||
|
#include "QuestScript.hh"
|
||||||
|
#include "StaticGameData.hh"
|
||||||
|
#include "TeamIndex.hh"
|
||||||
|
|
||||||
|
class QuestAvailabilityExpression {
|
||||||
|
public:
|
||||||
|
QuestAvailabilityExpression(const std::string& text);
|
||||||
|
~QuestAvailabilityExpression() = default;
|
||||||
|
inline bool operator==(const QuestAvailabilityExpression& other) const {
|
||||||
|
return this->root->operator==(*other.root);
|
||||||
|
}
|
||||||
|
inline bool operator!=(const QuestAvailabilityExpression& other) const {
|
||||||
|
return !this->operator==(other);
|
||||||
|
}
|
||||||
|
inline bool evaluate(const QuestFlagsForDifficulty& flags, std::shared_ptr<const TeamIndex::Team> team) const {
|
||||||
|
return this->root->evaluate(flags, team);
|
||||||
|
}
|
||||||
|
inline std::string str() const {
|
||||||
|
return this->root->str();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
class Node {
|
||||||
|
public:
|
||||||
|
virtual ~Node() = default;
|
||||||
|
virtual bool operator==(const Node& other) const = 0;
|
||||||
|
inline bool operator!=(const Node& other) const {
|
||||||
|
return !this->operator==(other);
|
||||||
|
}
|
||||||
|
virtual bool evaluate(const QuestFlagsForDifficulty& flags, std::shared_ptr<const TeamIndex::Team> team) const = 0;
|
||||||
|
virtual std::string str() const = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Node() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
class OrNode : public Node {
|
||||||
|
public:
|
||||||
|
OrNode(std::unique_ptr<const Node>&& left, std::unique_ptr<const Node>&& right);
|
||||||
|
virtual ~OrNode() = default;
|
||||||
|
virtual bool operator==(const Node& other) const;
|
||||||
|
virtual bool evaluate(const QuestFlagsForDifficulty& flags, std::shared_ptr<const TeamIndex::Team> team) const;
|
||||||
|
virtual std::string str() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::unique_ptr<const Node> left;
|
||||||
|
std::unique_ptr<const Node> right;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AndNode : public Node {
|
||||||
|
public:
|
||||||
|
AndNode(std::unique_ptr<const Node>&& left, std::unique_ptr<const Node>&& right);
|
||||||
|
virtual ~AndNode() = default;
|
||||||
|
virtual bool operator==(const Node& other) const;
|
||||||
|
virtual bool evaluate(const QuestFlagsForDifficulty& flags, std::shared_ptr<const TeamIndex::Team> team) const;
|
||||||
|
virtual std::string str() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::unique_ptr<const Node> left;
|
||||||
|
std::unique_ptr<const Node> right;
|
||||||
|
};
|
||||||
|
|
||||||
|
class NotNode : public Node {
|
||||||
|
public:
|
||||||
|
NotNode(std::unique_ptr<const Node>&& sub);
|
||||||
|
virtual ~NotNode() = default;
|
||||||
|
virtual bool operator==(const Node& other) const;
|
||||||
|
virtual bool evaluate(const QuestFlagsForDifficulty& flags, std::shared_ptr<const TeamIndex::Team> team) const;
|
||||||
|
virtual std::string str() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::unique_ptr<const Node> sub;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FlagLookupNode : public Node {
|
||||||
|
public:
|
||||||
|
FlagLookupNode(uint16_t flag_index);
|
||||||
|
virtual ~FlagLookupNode() = default;
|
||||||
|
virtual bool operator==(const Node& other) const;
|
||||||
|
virtual bool evaluate(const QuestFlagsForDifficulty& flags, std::shared_ptr<const TeamIndex::Team> team) const;
|
||||||
|
virtual std::string str() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint16_t flag_index;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TeamRewardLookupNode : public Node {
|
||||||
|
public:
|
||||||
|
TeamRewardLookupNode(const std::string& reward_name);
|
||||||
|
virtual ~TeamRewardLookupNode() = default;
|
||||||
|
virtual bool operator==(const Node& other) const;
|
||||||
|
virtual bool evaluate(const QuestFlagsForDifficulty& flags, std::shared_ptr<const TeamIndex::Team> team) const;
|
||||||
|
virtual std::string str() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::string reward_name;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ConstantNode : public Node {
|
||||||
|
public:
|
||||||
|
ConstantNode(bool value);
|
||||||
|
virtual ~ConstantNode() = default;
|
||||||
|
virtual bool operator==(const Node& other) const;
|
||||||
|
virtual bool evaluate(const QuestFlagsForDifficulty& flags, std::shared_ptr<const TeamIndex::Team> team) const;
|
||||||
|
virtual std::string str() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool value;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<const Node> parse_expr(std::string_view text);
|
||||||
|
|
||||||
|
std::unique_ptr<const Node> root;
|
||||||
|
};
|
||||||
@@ -2287,7 +2287,7 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
|||||||
send_lobby_message_box(c, "$C6Your level is too\nhigh to join this\ngame.");
|
send_lobby_message_box(c, "$C6Your level is too\nhigh to join this\ngame.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (game->quest && !c->can_access_quest(game->quest, game->difficulty)) {
|
if (game->quest && !c->can_play_quest(game->quest, game->difficulty)) {
|
||||||
send_lobby_message_box(c, "$C6You don't have access\nto the quest in progress\nin this game.");
|
send_lobby_message_box(c, "$C6You don't have access\nto the quest in progress\nin this game.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -2370,7 +2370,7 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
|
|||||||
send_lobby_message_box(c, "$C6A quest is already\nin progress.");
|
send_lobby_message_box(c, "$C6A quest is already\nin progress.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (!l->quest_include_condition()(q)) {
|
if (l->quest_include_condition()(q) != QuestIndex::IncludeState::AVAILABLE) {
|
||||||
send_lobby_message_box(c, "$C6This quest has not\nbeen unlocked for\nall players in this\ngame.");
|
send_lobby_message_box(c, "$C6This quest has not\nbeen unlocked for\nall players in this\ngame.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
+33
-7
@@ -1414,25 +1414,48 @@ template <typename EntryT>
|
|||||||
void send_quest_menu_t(
|
void send_quest_menu_t(
|
||||||
shared_ptr<Client> c,
|
shared_ptr<Client> c,
|
||||||
uint32_t menu_id,
|
uint32_t menu_id,
|
||||||
const vector<shared_ptr<const Quest>>& quests,
|
const vector<pair<QuestIndex::IncludeState, shared_ptr<const Quest>>>& quests,
|
||||||
bool is_download_menu) {
|
bool is_download_menu) {
|
||||||
auto v = c->version();
|
auto v = c->version();
|
||||||
vector<EntryT> entries;
|
vector<EntryT> entries;
|
||||||
for (const auto& quest : quests) {
|
for (const auto& it : quests) {
|
||||||
auto vq = quest->version(v, c->language());
|
auto vq = it.second->version(v, c->language());
|
||||||
if (!vq) {
|
if (!vq) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& e = entries.emplace_back();
|
auto& e = entries.emplace_back();
|
||||||
e.menu_id = menu_id;
|
e.menu_id = menu_id;
|
||||||
e.item_id = quest->quest_number;
|
e.item_id = it.second->quest_number;
|
||||||
e.name.encode(vq->name, c->language());
|
e.name.encode(vq->name, c->language());
|
||||||
e.short_description.encode(add_color(vq->short_description), c->language());
|
e.short_description.encode(add_color(vq->short_description), c->language());
|
||||||
}
|
}
|
||||||
send_command_vt(c, is_download_menu ? 0xA4 : 0xA2, entries.size(), entries);
|
send_command_vt(c, is_download_menu ? 0xA4 : 0xA2, entries.size(), entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void send_quest_menu_bb(
|
||||||
|
shared_ptr<Client> c,
|
||||||
|
uint32_t menu_id,
|
||||||
|
const vector<pair<QuestIndex::IncludeState, shared_ptr<const Quest>>>& quests,
|
||||||
|
bool is_download_menu) {
|
||||||
|
auto v = c->version();
|
||||||
|
vector<S_QuestMenuEntry_BB_A2_A4> entries;
|
||||||
|
for (const auto& it : quests) {
|
||||||
|
auto vq = it.second->version(v, c->language());
|
||||||
|
if (!vq) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& e = entries.emplace_back();
|
||||||
|
e.menu_id = menu_id;
|
||||||
|
e.item_id = it.second->quest_number;
|
||||||
|
e.name.encode(vq->name, c->language());
|
||||||
|
e.short_description.encode(add_color(vq->short_description), c->language());
|
||||||
|
e.disabled = (it.first == QuestIndex::IncludeState::DISABLED) ? 1 : 0;
|
||||||
|
}
|
||||||
|
send_command_vt(c, is_download_menu ? 0xA4 : 0xA2, entries.size(), entries);
|
||||||
|
}
|
||||||
|
|
||||||
template <typename EntryT>
|
template <typename EntryT>
|
||||||
void send_quest_categories_menu_t(
|
void send_quest_categories_menu_t(
|
||||||
shared_ptr<Client> c,
|
shared_ptr<Client> c,
|
||||||
@@ -1459,8 +1482,11 @@ void send_quest_categories_menu_t(
|
|||||||
send_command_vt(c, is_download_menu ? 0xA4 : 0xA2, entries.size(), entries);
|
send_command_vt(c, is_download_menu ? 0xA4 : 0xA2, entries.size(), entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
void send_quest_menu(shared_ptr<Client> c, uint32_t menu_id,
|
void send_quest_menu(
|
||||||
const vector<shared_ptr<const Quest>>& quests, bool is_download_menu) {
|
shared_ptr<Client> c,
|
||||||
|
uint32_t menu_id,
|
||||||
|
const vector<pair<QuestIndex::IncludeState, shared_ptr<const Quest>>>& quests,
|
||||||
|
bool is_download_menu) {
|
||||||
switch (c->version()) {
|
switch (c->version()) {
|
||||||
case Version::PC_V2:
|
case Version::PC_V2:
|
||||||
send_quest_menu_t<S_QuestMenuEntry_PC_A2_A4>(c, menu_id, quests, is_download_menu);
|
send_quest_menu_t<S_QuestMenuEntry_PC_A2_A4>(c, menu_id, quests, is_download_menu);
|
||||||
@@ -1479,7 +1505,7 @@ void send_quest_menu(shared_ptr<Client> c, uint32_t menu_id,
|
|||||||
send_quest_menu_t<S_QuestMenuEntry_XB_A2_A4>(c, menu_id, quests, is_download_menu);
|
send_quest_menu_t<S_QuestMenuEntry_XB_A2_A4>(c, menu_id, quests, is_download_menu);
|
||||||
break;
|
break;
|
||||||
case Version::BB_V4:
|
case Version::BB_V4:
|
||||||
send_quest_menu_t<S_QuestMenuEntry_BB_A2_A4>(c, menu_id, quests, is_download_menu);
|
send_quest_menu_bb(c, menu_id, quests, is_download_menu);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw logic_error("unimplemented versioned command");
|
throw logic_error("unimplemented versioned command");
|
||||||
|
|||||||
+1
-1
@@ -258,7 +258,7 @@ void send_game_menu(
|
|||||||
void send_quest_menu(
|
void send_quest_menu(
|
||||||
std::shared_ptr<Client> c,
|
std::shared_ptr<Client> c,
|
||||||
uint32_t menu_id,
|
uint32_t menu_id,
|
||||||
const std::vector<std::shared_ptr<const Quest>>& quests,
|
const std::vector<std::pair<QuestIndex::IncludeState, std::shared_ptr<const Quest>>>& quests,
|
||||||
bool is_download_menu);
|
bool is_download_menu);
|
||||||
void send_quest_categories_menu(
|
void send_quest_categories_menu(
|
||||||
std::shared_ptr<Client> c,
|
std::shared_ptr<Client> c,
|
||||||
|
|||||||
@@ -501,7 +501,8 @@
|
|||||||
[0x02, "battle", "Battle", "$E$C6Battle mode rule\nsets"],
|
[0x02, "battle", "Battle", "$E$C6Battle mode rule\nsets"],
|
||||||
[0x04, "challenge-ep1", "Challenge (Episode 1)", "$E$C6Challenge mode\nquests in Episode 1"],
|
[0x04, "challenge-ep1", "Challenge (Episode 1)", "$E$C6Challenge mode\nquests in Episode 1"],
|
||||||
[0x84, "challenge-ep2", "Challenge (Episode 2)", "$E$C6Challenge mode\nquests in Episode 2"],
|
[0x84, "challenge-ep2", "Challenge (Episode 2)", "$E$C6Challenge mode\nquests in Episode 2"],
|
||||||
[0x08, "solo", "Solo", "$E$C6Quests that require\na single player"],
|
[0x08, "solo-story", "Story", "$E$C6Quests that follow\nthe Episode 1 story"],
|
||||||
|
[0x08, "solo-extra", "Solo", "$E$C6Quests that require\na single player"],
|
||||||
[0x10, "government-ep1", "Hero in Red", "$E$CG-Red Ring Rico-\n$C6Quests that follow\nthe Episode 1\nstoryline"],
|
[0x10, "government-ep1", "Hero in Red", "$E$CG-Red Ring Rico-\n$C6Quests that follow\nthe Episode 1\nstoryline"],
|
||||||
[0x10, "government-ep2", "The Military's Hero", "$E$CG-Heathcliff Flowen-\n$C6Quests that follow\nthe Episode 2\nstoryline"],
|
[0x10, "government-ep2", "The Military's Hero", "$E$CG-Heathcliff Flowen-\n$C6Quests that follow\nthe Episode 2\nstoryline"],
|
||||||
[0x10, "government-ep4", "The Meteor Impact Incident", "$E$C6Quests that follow\nthe Episode 4\nstoryline"],
|
[0x10, "government-ep4", "The Meteor Impact Incident", "$E$C6Quests that follow\nthe Episode 4\nstoryline"],
|
||||||
|
|||||||
@@ -35,14 +35,16 @@
|
|||||||
// "ChallengeTemplateIndex": 0,
|
// "ChallengeTemplateIndex": 0,
|
||||||
|
|
||||||
// Quests may be set to be unavailable until a preceding quest has been
|
// 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
|
// cleared or a team reward has been purchased. To enable this feature, set a
|
||||||
// JSON file. This field is ignored if the player has the
|
// value for AvailableIf in the quest's JSON file. This field's value should
|
||||||
// DISABLE_QUEST_REQUIREMENTS flag in their license.
|
// be a boolean expression that tests one or more flags or team rewards. An
|
||||||
// "RequireFlag": 0x01F5,
|
// example with random values is shown below. This field is ignored if the
|
||||||
|
// player has the DISABLE_QUEST_REQUIREMENTS flag in their license.
|
||||||
|
// "AvailableIf": "(F_016D || F_0171 || T_EpicCustomQuest) && !F_0173",
|
||||||
|
|
||||||
// Quests on BB may be set to be available only through a team reward. To
|
// On BB, quests may be disabled but still visible to the player. This
|
||||||
// enable this feature, set a value for RequireTeamRewardKey in the quest's
|
// expression controls when that should be the case. If AvailableIf evaluates
|
||||||
// JSON file. This field is ignored if the player has the
|
// to false, this is ignored. This field is also ignored if the player has
|
||||||
// DISABLE_QUEST_REQUIREMENTS flag in their license.
|
// the DISABLE_QUEST_REQUIREMENTS flag in their license.
|
||||||
// "RequireTeamRewardKey": "PointOfDisasterQuest",
|
// "EnabledIf": "!F_0169",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"RequireFlag": 0x01F5,
|
"AvailableIf": "F_01F5",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"RequireFlag": 0x01F7,
|
"AvailableIf": "F_01F7",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"RequireFlag": 0x01F9,
|
"AvailableIf": "F_01F9",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"RequireFlag": 0x01FB,
|
"AvailableIf": "F_01FB",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"RequireFlag": 0x01FD,
|
"AvailableIf": "F_01FD",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"RequireFlag": 0x01FF,
|
"AvailableIf": "F_01FF",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"RequireFlag": 0x0201,
|
"AvailableIf": "F_0201",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"RequireFlag": 0x0203,
|
"AvailableIf": "F_0203",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"RequireFlag": 0x0205,
|
"AvailableIf": "F_0205",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"RequireFlag": 0x0207,
|
"AvailableIf": "F_0207",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"RequireFlag": 0x0209,
|
"AvailableIf": "F_0209",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"RequireFlag": 0x020B,
|
"AvailableIf": "F_020B",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"RequireFlag": 0x020D,
|
"AvailableIf": "F_020D",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"RequireFlag": 0x020F,
|
"AvailableIf": "F_020F",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"RequireFlag": 0x0213,
|
"AvailableIf": "F_0213",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"RequireFlag": 0x0215,
|
"AvailableIf": "F_0215",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"RequireFlag": 0x0217,
|
"AvailableIf": "F_0217",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"RequireFlag": 0x0219,
|
"AvailableIf": "F_0219",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"RequireFlag": 0x021B,
|
"AvailableIf": "F_021B",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"RequireFlag": 0x021D,
|
"AvailableIf": "F_021D",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"RequireFlag": 0x021F,
|
"AvailableIf": "F_021F",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"RequireFlag": 0x0221,
|
"AvailableIf": "F_0221",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"RequireFlag": 0x0223,
|
"AvailableIf": "F_0223",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"RequireFlag": 0x0225,
|
"AvailableIf": "F_0225",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"RequireFlag": 0x0227,
|
"AvailableIf": "F_0227",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"RequireFlag": 0x0229,
|
"AvailableIf": "F_0229",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"RequireFlag": 0x022B,
|
"AvailableIf": "F_022B",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"RequireFlag": 0x022D,
|
"AvailableIf": "F_022D",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"RequireFlag": 0x022F,
|
"AvailableIf": "F_022F",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"RequireFlag": 0x0231,
|
"AvailableIf": "F_0231",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"RequireFlag": 0x0233,
|
"AvailableIf": "F_0233",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"RequireFlag": 0x02BD,
|
"AvailableIf": "F_02BD",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"RequireFlag": 0x02BE,
|
"AvailableIf": "F_02BE",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"RequireFlag": 0x02BF,
|
"AvailableIf": "F_02BF",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"RequireFlag": 0x02C0,
|
"AvailableIf": "F_02C0",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"RequireFlag": 0x02C1,
|
"AvailableIf": "F_02C1",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"RequireFlag": 0x02C2,
|
"AvailableIf": "F_02C2",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"RequireFlag": 0x02C3,
|
"AvailableIf": "F_02C3",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"EnabledIf": "!F_0065 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)",
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"EnabledIf": "!F_0067 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)",
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"AvailableIf": "F_0065 && F_0067 && F_006B && F_01F9",
|
||||||
|
"EnabledIf": "!F_0069 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)",
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"EnabledIf": "!F_006B || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)",
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"AvailableIf": "F_0065 && F_0067 && F_006B",
|
||||||
|
"EnabledIf": "!F_006D || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)",
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"AvailableIf": "F_0065 && F_0067 && F_006B",
|
||||||
|
"EnabledIf": "!F_006F || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)",
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"AvailableIf": "F_0065 && F_0067 && F_006B",
|
||||||
|
"EnabledIf": "!F_0071 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)",
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"AvailableIf": "F_0071",
|
||||||
|
"EnabledIf": "!F_0073 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)",
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"AvailableIf": "F_0065 && F_0067 && F_006B",
|
||||||
|
"EnabledIf": "!F_0075 || (F_0065 && F_0067 && F_0069 && F_006B && F_006D && F_006F && F_0071 && F_0073 && F_0075 && F_0077 && F_0079 && F_007B && F_007D && F_007F && F_0081 && F_0083 && F_0085 && F_0087 && F_0089 && F_008B && F_008D && F_008F && F_0091 && F_0093 && F_0095)",
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user