implement quest unlock flags
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user