replace UnlockAllAreas and PreventPersistQuestFlags with generalized rewrite map

This commit is contained in:
Martin Michelsen
2024-03-30 22:35:34 -07:00
parent 7c7df39e6d
commit 4a3b0118a8
13 changed files with 209 additions and 130 deletions
+1 -1
View File
@@ -114,7 +114,7 @@ set(SOURCES
src/PSOGCObjectGraph.cc
src/PSOProtocol.cc
src/Quest.cc
src/QuestAvailabilityExpression.cc
src/IntegralExpression.cc
src/QuestScript.cc
src/RareItemSet.cc
src/ReceiveCommands.cc
+3 -3
View File
@@ -350,7 +350,7 @@ shared_ptr<const TeamIndex::Team> Client::team() const {
}
bool Client::evaluate_quest_availability_expression(
shared_ptr<const QuestAvailabilityExpression> expr,
shared_ptr<const IntegralExpression> expr,
shared_ptr<const Lobby> game,
uint8_t event,
uint8_t difficulty,
@@ -366,8 +366,8 @@ bool Client::evaluate_quest_availability_expression(
throw logic_error("quest flags are missing from game");
}
auto p = this->character();
QuestAvailabilityExpression::Env env = {
.flags = (game && !game->quest_flags_known) ? &game->quest_flag_values->data.at(difficulty) : &p->quest_flags.data.at(difficulty),
IntegralExpression::Env env = {
.flags = &p->quest_flags.data.at(difficulty),
.challenge_records = &p->challenge_records,
.team = this->team(),
.num_players = num_players,
+1 -1
View File
@@ -294,7 +294,7 @@ public:
std::shared_ptr<const TeamIndex::Team> team() const;
bool evaluate_quest_availability_expression(
std::shared_ptr<const QuestAvailabilityExpression> expr,
std::shared_ptr<const IntegralExpression> expr,
std::shared_ptr<const Lobby> game,
uint8_t event,
uint8_t difficulty,
@@ -1,4 +1,4 @@
#include "QuestAvailabilityExpression.hh"
#include "IntegralExpression.hh"
#include <algorithm>
#include <mutex>
@@ -21,16 +21,16 @@
using namespace std;
QuestAvailabilityExpression::QuestAvailabilityExpression(const string& text)
IntegralExpression::IntegralExpression(const string& text)
: root(this->parse_expr(text)) {}
QuestAvailabilityExpression::BinaryOperatorNode::BinaryOperatorNode(
IntegralExpression::BinaryOperatorNode::BinaryOperatorNode(
Type type, unique_ptr<const Node>&& left, unique_ptr<const Node>&& right)
: type(type),
left(std::move(left)),
right(std::move(right)) {}
bool QuestAvailabilityExpression::BinaryOperatorNode::operator==(const Node& other) const {
bool IntegralExpression::BinaryOperatorNode::operator==(const Node& other) const {
try {
const BinaryOperatorNode& other_bin = dynamic_cast<const BinaryOperatorNode&>(other);
return other_bin.type == this->type && *other_bin.left == *this->left && *other_bin.right == *this->right;
@@ -39,7 +39,7 @@ bool QuestAvailabilityExpression::BinaryOperatorNode::operator==(const Node& oth
}
}
int64_t QuestAvailabilityExpression::BinaryOperatorNode::evaluate(const Env& env) const {
int64_t IntegralExpression::BinaryOperatorNode::evaluate(const Env& env) const {
switch (this->type) {
case Type::LOGICAL_OR:
return this->left->evaluate(env) || this->right->evaluate(env);
@@ -82,7 +82,7 @@ int64_t QuestAvailabilityExpression::BinaryOperatorNode::evaluate(const Env& env
}
}
string QuestAvailabilityExpression::BinaryOperatorNode::str() const {
string IntegralExpression::BinaryOperatorNode::str() const {
switch (this->type) {
case Type::LOGICAL_OR:
return "(" + this->left->str() + ") || (" + this->right->str() + ")";
@@ -125,11 +125,11 @@ string QuestAvailabilityExpression::BinaryOperatorNode::str() const {
}
}
QuestAvailabilityExpression::UnaryOperatorNode::UnaryOperatorNode(Type type, unique_ptr<const Node>&& sub)
IntegralExpression::UnaryOperatorNode::UnaryOperatorNode(Type type, unique_ptr<const Node>&& sub)
: type(type),
sub(std::move(sub)) {}
bool QuestAvailabilityExpression::UnaryOperatorNode::operator==(const Node& other) const {
bool IntegralExpression::UnaryOperatorNode::operator==(const Node& other) const {
try {
const UnaryOperatorNode& other_un = dynamic_cast<const UnaryOperatorNode&>(other);
return other_un.type == this->type && *other_un.sub == *this->sub;
@@ -138,7 +138,7 @@ bool QuestAvailabilityExpression::UnaryOperatorNode::operator==(const Node& othe
}
}
int64_t QuestAvailabilityExpression::UnaryOperatorNode::evaluate(const Env& env) const {
int64_t IntegralExpression::UnaryOperatorNode::evaluate(const Env& env) const {
switch (this->type) {
case Type::LOGICAL_NOT:
return !this->sub->evaluate(env);
@@ -151,7 +151,7 @@ int64_t QuestAvailabilityExpression::UnaryOperatorNode::evaluate(const Env& env)
}
}
string QuestAvailabilityExpression::UnaryOperatorNode::str() const {
string IntegralExpression::UnaryOperatorNode::str() const {
switch (this->type) {
case Type::LOGICAL_NOT:
return "!(" + this->sub->str() + ")";
@@ -164,10 +164,10 @@ string QuestAvailabilityExpression::UnaryOperatorNode::str() const {
}
}
QuestAvailabilityExpression::FlagLookupNode::FlagLookupNode(uint16_t flag_index)
IntegralExpression::FlagLookupNode::FlagLookupNode(uint16_t flag_index)
: flag_index(flag_index) {}
bool QuestAvailabilityExpression::FlagLookupNode::operator==(const Node& other) const {
bool IntegralExpression::FlagLookupNode::operator==(const Node& other) const {
try {
const FlagLookupNode& other_flag = dynamic_cast<const FlagLookupNode&>(other);
return other_flag.flag_index == this->flag_index;
@@ -176,20 +176,23 @@ bool QuestAvailabilityExpression::FlagLookupNode::operator==(const Node& other)
}
}
int64_t QuestAvailabilityExpression::FlagLookupNode::evaluate(const Env& env) const {
int64_t IntegralExpression::FlagLookupNode::evaluate(const Env& env) const {
if (!env.flags) {
throw runtime_error("quest flags not available");
}
return env.flags->get(this->flag_index) ? 1 : 0;
}
string QuestAvailabilityExpression::FlagLookupNode::str() const {
string IntegralExpression::FlagLookupNode::str() const {
return string_printf("F_%04hX", this->flag_index);
}
QuestAvailabilityExpression::ChallengeCompletionLookupNode::ChallengeCompletionLookupNode(
IntegralExpression::ChallengeCompletionLookupNode::ChallengeCompletionLookupNode(
Episode episode, uint8_t stage_index)
: episode(episode),
stage_index(stage_index) {}
bool QuestAvailabilityExpression::ChallengeCompletionLookupNode::operator==(const Node& other) const {
bool IntegralExpression::ChallengeCompletionLookupNode::operator==(const Node& other) const {
try {
const ChallengeCompletionLookupNode& other_cc = dynamic_cast<const ChallengeCompletionLookupNode&>(other);
return other_cc.episode == this->episode && other_cc.stage_index == this->stage_index;
@@ -198,7 +201,10 @@ bool QuestAvailabilityExpression::ChallengeCompletionLookupNode::operator==(cons
}
}
int64_t QuestAvailabilityExpression::ChallengeCompletionLookupNode::evaluate(const Env& env) const {
int64_t IntegralExpression::ChallengeCompletionLookupNode::evaluate(const Env& env) const {
if (!env.challenge_records) {
throw runtime_error("challenge records not available");
}
if (this->episode == Episode::EP1) {
return env.challenge_records->times_ep1_online.at(this->stage_index).has_value();
} else if (this->episode == Episode::EP2) {
@@ -207,14 +213,14 @@ int64_t QuestAvailabilityExpression::ChallengeCompletionLookupNode::evaluate(con
return false;
}
string QuestAvailabilityExpression::ChallengeCompletionLookupNode::str() const {
string IntegralExpression::ChallengeCompletionLookupNode::str() const {
return string_printf("CC_%s_%hhu", abbreviation_for_episode(this->episode), static_cast<uint8_t>(this->stage_index + 1));
}
QuestAvailabilityExpression::TeamRewardLookupNode::TeamRewardLookupNode(const string& reward_name)
IntegralExpression::TeamRewardLookupNode::TeamRewardLookupNode(const string& reward_name)
: reward_name(reward_name) {}
bool QuestAvailabilityExpression::TeamRewardLookupNode::operator==(const Node& other) const {
bool IntegralExpression::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;
@@ -223,60 +229,60 @@ bool QuestAvailabilityExpression::TeamRewardLookupNode::operator==(const Node& o
}
}
int64_t QuestAvailabilityExpression::TeamRewardLookupNode::evaluate(const Env& env) const {
int64_t IntegralExpression::TeamRewardLookupNode::evaluate(const Env& env) const {
return (env.team && env.team->has_reward(this->reward_name)) ? 1 : 0;
}
string QuestAvailabilityExpression::TeamRewardLookupNode::str() const {
string IntegralExpression::TeamRewardLookupNode::str() const {
return "T_" + this->reward_name;
}
QuestAvailabilityExpression::NumPlayersLookupNode::NumPlayersLookupNode() {}
IntegralExpression::NumPlayersLookupNode::NumPlayersLookupNode() {}
bool QuestAvailabilityExpression::NumPlayersLookupNode::operator==(const Node& other) const {
bool IntegralExpression::NumPlayersLookupNode::operator==(const Node& other) const {
return dynamic_cast<const NumPlayersLookupNode*>(&other) != nullptr;
}
int64_t QuestAvailabilityExpression::NumPlayersLookupNode::evaluate(const Env& env) const {
int64_t IntegralExpression::NumPlayersLookupNode::evaluate(const Env& env) const {
return env.num_players;
}
string QuestAvailabilityExpression::NumPlayersLookupNode::str() const {
string IntegralExpression::NumPlayersLookupNode::str() const {
return "V_NumPlayers";
}
QuestAvailabilityExpression::EventLookupNode::EventLookupNode() {}
IntegralExpression::EventLookupNode::EventLookupNode() {}
bool QuestAvailabilityExpression::EventLookupNode::operator==(const Node& other) const {
bool IntegralExpression::EventLookupNode::operator==(const Node& other) const {
return dynamic_cast<const EventLookupNode*>(&other) != nullptr;
}
int64_t QuestAvailabilityExpression::EventLookupNode::evaluate(const Env& env) const {
int64_t IntegralExpression::EventLookupNode::evaluate(const Env& env) const {
return env.event;
}
string QuestAvailabilityExpression::EventLookupNode::str() const {
string IntegralExpression::EventLookupNode::str() const {
return "V_Event";
}
QuestAvailabilityExpression::V1PresenceLookupNode::V1PresenceLookupNode() {}
IntegralExpression::V1PresenceLookupNode::V1PresenceLookupNode() {}
bool QuestAvailabilityExpression::V1PresenceLookupNode::operator==(const Node& other) const {
bool IntegralExpression::V1PresenceLookupNode::operator==(const Node& other) const {
return dynamic_cast<const V1PresenceLookupNode*>(&other) != nullptr;
}
int64_t QuestAvailabilityExpression::V1PresenceLookupNode::evaluate(const Env& env) const {
int64_t IntegralExpression::V1PresenceLookupNode::evaluate(const Env& env) const {
return env.v1_present ? 1 : 0;
}
string QuestAvailabilityExpression::V1PresenceLookupNode::str() const {
string IntegralExpression::V1PresenceLookupNode::str() const {
return "V_V1Present";
}
QuestAvailabilityExpression::ConstantNode::ConstantNode(int64_t value)
IntegralExpression::ConstantNode::ConstantNode(int64_t value)
: value(value) {}
bool QuestAvailabilityExpression::ConstantNode::operator==(const Node& other) const {
bool IntegralExpression::ConstantNode::operator==(const Node& other) const {
try {
const ConstantNode& other_const = dynamic_cast<const ConstantNode&>(other);
return other_const.value == this->value;
@@ -285,15 +291,15 @@ bool QuestAvailabilityExpression::ConstantNode::operator==(const Node& other) co
}
}
int64_t QuestAvailabilityExpression::ConstantNode::evaluate(const Env&) const {
int64_t IntegralExpression::ConstantNode::evaluate(const Env&) const {
return this->value;
}
string QuestAvailabilityExpression::ConstantNode::str() const {
string IntegralExpression::ConstantNode::str() const {
return string_printf("%" PRId64, this->value);
}
unique_ptr<const QuestAvailabilityExpression::Node> QuestAvailabilityExpression::parse_expr(string_view text) {
unique_ptr<const IntegralExpression::Node> IntegralExpression::parse_expr(string_view text) {
// Strip off spaces and fully-enclosing parentheses
for (;;) {
size_t starting_size = text.size();
@@ -334,13 +340,13 @@ unique_ptr<const QuestAvailabilityExpression::Node> QuestAvailabilityExpression:
// Check for unary operators
if (text[0] == '!') {
return make_unique<UnaryOperatorNode>(UnaryOperatorNode::Type::LOGICAL_NOT,
QuestAvailabilityExpression::parse_expr(text.substr(1)));
IntegralExpression::parse_expr(text.substr(1)));
} else if (text[0] == '~') {
return make_unique<UnaryOperatorNode>(UnaryOperatorNode::Type::BITWISE_NOT,
QuestAvailabilityExpression::parse_expr(text.substr(1)));
IntegralExpression::parse_expr(text.substr(1)));
} else if (text[0] == '-') {
return make_unique<UnaryOperatorNode>(UnaryOperatorNode::Type::NEGATIVE,
QuestAvailabilityExpression::parse_expr(text.substr(1)));
IntegralExpression::parse_expr(text.substr(1)));
}
// Check for binary operators at the root level
@@ -377,8 +383,8 @@ unique_ptr<const QuestAvailabilityExpression::Node> QuestAvailabilityExpression:
((z < oper.first.size()) || (text.compare(z - oper.first.size(), oper.first.size(), oper.first) != 0)) &&
(text.compare(z, oper.first.size(), oper.first) == 0) &&
(text.compare(z + oper.first.size(), oper.first.size(), oper.first) != 0)) {
auto left = QuestAvailabilityExpression::parse_expr(text.substr(0, z));
auto right = QuestAvailabilityExpression::parse_expr(text.substr(z + oper.first.size()));
auto left = IntegralExpression::parse_expr(text.substr(0, z));
auto right = IntegralExpression::parse_expr(text.substr(z + oper.first.size()));
return make_unique<BinaryOperatorNode>(oper.second, std::move(left), std::move(right));
}
}
@@ -13,7 +13,7 @@
#include "StaticGameData.hh"
#include "TeamIndex.hh"
class QuestAvailabilityExpression {
class IntegralExpression {
public:
struct Env {
const QuestFlagsForDifficulty* flags;
@@ -24,12 +24,12 @@ public:
bool v1_present;
};
QuestAvailabilityExpression(const std::string& text);
~QuestAvailabilityExpression() = default;
inline bool operator==(const QuestAvailabilityExpression& other) const {
IntegralExpression(const std::string& text);
~IntegralExpression() = default;
inline bool operator==(const IntegralExpression& other) const {
return this->root->operator==(*other.root);
}
inline bool operator!=(const QuestAvailabilityExpression& other) const {
inline bool operator!=(const IntegralExpression& other) const {
return !this->operator==(other);
}
inline int64_t evaluate(const Env& env) const {
+6 -6
View File
@@ -205,8 +205,8 @@ VersionedQuest::VersionedQuest(
std::shared_ptr<const std::string> pvr_contents,
std::shared_ptr<const BattleRules> battle_rules,
ssize_t challenge_template_index,
std::shared_ptr<const QuestAvailabilityExpression> available_expression,
std::shared_ptr<const QuestAvailabilityExpression> enabled_expression)
std::shared_ptr<const IntegralExpression> available_expression,
std::shared_ptr<const IntegralExpression> enabled_expression)
: quest_number(quest_number),
category_id(category_id),
episode(Episode::NONE),
@@ -673,8 +673,8 @@ QuestIndex::QuestIndex(
const FileData* json_filedata = nullptr;
shared_ptr<BattleRules> battle_rules;
ssize_t challenge_template_index = -1;
shared_ptr<const QuestAvailabilityExpression> available_expression;
shared_ptr<const QuestAvailabilityExpression> enabled_expression;
shared_ptr<const IntegralExpression> available_expression;
shared_ptr<const IntegralExpression> enabled_expression;
try {
json_filedata = &json_files.at(basename);
} catch (const out_of_range&) {
@@ -698,11 +698,11 @@ QuestIndex::QuestIndex(
} catch (const out_of_range&) {
}
try {
available_expression = make_shared<QuestAvailabilityExpression>(metadata_json.get_string("AvailableIf"));
available_expression = make_shared<IntegralExpression>(metadata_json.get_string("AvailableIf"));
} catch (const out_of_range&) {
}
try {
enabled_expression = make_shared<QuestAvailabilityExpression>(metadata_json.get_string("EnabledIf"));
enabled_expression = make_shared<IntegralExpression>(metadata_json.get_string("EnabledIf"));
} catch (const out_of_range&) {
}
}
+7 -7
View File
@@ -8,8 +8,8 @@
#include <unordered_map>
#include <vector>
#include "IntegralExpression.hh"
#include "PlayerSubordinates.hh"
#include "QuestAvailabilityExpression.hh"
#include "QuestScript.hh"
#include "StaticGameData.hh"
#include "TeamIndex.hh"
@@ -76,8 +76,8 @@ struct VersionedQuest {
std::shared_ptr<const std::string> pvr_contents;
std::shared_ptr<const BattleRules> battle_rules;
ssize_t challenge_template_index;
std::shared_ptr<const QuestAvailabilityExpression> available_expression;
std::shared_ptr<const QuestAvailabilityExpression> enabled_expression;
std::shared_ptr<const IntegralExpression> available_expression;
std::shared_ptr<const IntegralExpression> enabled_expression;
VersionedQuest(
uint32_t quest_number,
@@ -89,8 +89,8 @@ struct VersionedQuest {
std::shared_ptr<const std::string> pvr_contents,
std::shared_ptr<const BattleRules> battle_rules = nullptr,
ssize_t challenge_template_index = -1,
std::shared_ptr<const QuestAvailabilityExpression> available_expression = nullptr,
std::shared_ptr<const QuestAvailabilityExpression> enabled_expression = nullptr);
std::shared_ptr<const IntegralExpression> available_expression = nullptr,
std::shared_ptr<const IntegralExpression> enabled_expression = nullptr);
std::string bin_filename() const;
std::string dat_filename() const;
@@ -124,8 +124,8 @@ public:
std::string name;
std::shared_ptr<const BattleRules> battle_rules;
ssize_t challenge_template_index;
std::shared_ptr<const QuestAvailabilityExpression> available_expression;
std::shared_ptr<const QuestAvailabilityExpression> enabled_expression;
std::shared_ptr<const IntegralExpression> available_expression;
std::shared_ptr<const IntegralExpression> enabled_expression;
std::map<uint32_t, std::shared_ptr<const VersionedQuest>> versions;
};
+25 -29
View File
@@ -4219,6 +4219,7 @@ shared_ptr<Lobby> create_game_generic(
game->base_exp_multiplier = s->bb_global_exp_multiplier;
}
const unordered_map<uint16_t, IntegralExpression>* quest_flag_rewrites;
switch (game->base_version) {
case Version::DC_NTE:
case Version::DC_V1_11_2000_PROTOTYPE:
@@ -4226,6 +4227,7 @@ shared_ptr<Lobby> create_game_generic(
case Version::DC_V2:
case Version::PC_NTE:
case Version::PC_V2:
quest_flag_rewrites = &s->quest_flag_rewrites_v1_v2;
if (game->mode == GameMode::BATTLE) {
game->set_drop_mode(s->default_drop_mode_v1_v2_battle);
game->allowed_drop_modes = s->allowed_drop_modes_v1_v2_battle;
@@ -4240,6 +4242,7 @@ shared_ptr<Lobby> create_game_generic(
case Version::GC_NTE:
case Version::GC_V3:
case Version::XB_V3:
quest_flag_rewrites = &s->quest_flag_rewrites_v3;
if (game->mode == GameMode::BATTLE) {
game->set_drop_mode(s->default_drop_mode_v3_battle);
game->allowed_drop_modes = s->allowed_drop_modes_v3_battle;
@@ -4253,10 +4256,12 @@ shared_ptr<Lobby> create_game_generic(
break;
case Version::GC_EP3_NTE:
case Version::GC_EP3:
quest_flag_rewrites = nullptr;
game->set_drop_mode(Lobby::DropMode::DISABLED);
game->allowed_drop_modes = (1 << static_cast<size_t>(game->drop_mode));
break;
case Version::BB_V4:
quest_flag_rewrites = &s->quest_flag_rewrites_v4;
if (game->mode == GameMode::BATTLE) {
game->set_drop_mode(s->default_drop_mode_v4_battle);
game->allowed_drop_modes = s->allowed_drop_modes_v4_battle;
@@ -4323,37 +4328,28 @@ shared_ptr<Lobby> create_game_generic(
game->quest_flags_known = make_unique<QuestFlags>();
}
if (s->unlock_all_areas) {
static const vector<uint16_t> flags_ep1_v123 = {0x0017, 0x0020, 0x002A};
static const vector<uint16_t> flags_ep1_v4 = {0x01F9, 0x0201, 0x0207};
static const vector<uint16_t> flags_ep2_v123 = {0x004C, 0x004F, 0x0052};
static const vector<uint16_t> flags_ep2_v4 = {0x021B, 0x0225, 0x022F};
static const vector<uint16_t> flags_ep4_v4 = {0x02BD, 0x02BE, 0x02BF, 0x02C0, 0x02C1};
const vector<uint16_t>* flags_to_enable;
switch (game->episode) {
case Episode::EP1:
flags_to_enable = is_v4(game->base_version) ? &flags_ep1_v4 : &flags_ep1_v123;
break;
case Episode::EP2:
flags_to_enable = is_v4(game->base_version) ? &flags_ep2_v4 : &flags_ep2_v123;
break;
case Episode::EP4:
flags_to_enable = &flags_ep1_v4;
break;
default:
flags_to_enable = nullptr;
}
if (flags_to_enable) {
for (uint16_t flag_num : *flags_to_enable) {
game->quest_flag_values->set(game->difficulty, flag_num);
if (game->quest_flags_known) {
game->quest_flags_known->set(game->difficulty, flag_num);
}
if (quest_flag_rewrites && !quest_flag_rewrites->empty()) {
IntegralExpression::Env env = {
.flags = &p->quest_flags.data.at(difficulty),
.challenge_records = &p->challenge_records,
.team = c->team(),
.num_players = 1,
.event = game->event,
.v1_present = is_v1(game->base_version),
};
for (const auto& it : *quest_flag_rewrites) {
bool should_set = it.second.evaluate(env);
game->log.info("Overriding quest flag %04hX = %s", it.first, should_set ? "true" : "false");
if (should_set) {
game->quest_flag_values->set(game->difficulty, it.first);
} else {
game->quest_flag_values->clear(game->difficulty, it.first);
}
if (game->quest_flags_known) {
game->quest_flags_known->set(game->difficulty, it.first);
}
c->config.set_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_FLAG_STATE);
}
c->config.set_flag(Client::Flag::SHOULD_SEND_ARTIFICIAL_FLAG_STATE);
}
game->switch_flags = make_unique<SwitchFlags>();
+5 -9
View File
@@ -2717,16 +2717,12 @@ static void on_set_quest_flag(shared_ptr<Client> c, uint8_t command, uint8_t fla
auto s = c->require_server_state();
// TODO: Should we allow overlays here?
auto p = c->character(true, false);
if (s->quest_flag_persist_mask.get(flag_num)) {
if (should_set) {
c->log.info("Setting quest flag %s:%03hX", name_for_difficulty(difficulty), flag_num);
p->quest_flags.set(difficulty, flag_num);
} else {
c->log.info("Clearing quest flag %s:%03hX", name_for_difficulty(difficulty), flag_num);
p->quest_flags.clear(difficulty, flag_num);
}
if (should_set) {
c->log.info("Setting quest flag %s:%03hX", name_for_difficulty(difficulty), flag_num);
p->quest_flags.set(difficulty, flag_num);
} else {
c->log.info("Quest flag %s:%03hX cannot be modified", name_for_difficulty(difficulty), flag_num);
c->log.info("Clearing quest flag %s:%03hX", name_for_difficulty(difficulty), flag_num);
p->quest_flags.clear(difficulty, flag_num);
}
}
+20 -7
View File
@@ -718,13 +718,27 @@ void ServerState::load_config_early() {
throw runtime_error("CLIENT drop mode cannot be allowed in V4");
}
this->quest_flag_persist_mask.update_all(true);
try {
for (const auto& flag_id_json : this->config_json->get_list("PreventPersistQuestFlags")) {
this->quest_flag_persist_mask.clear(flag_id_json->as_int());
auto parse_quest_flag_rewrites = [&json = this->config_json](const char* key) -> std::unordered_map<uint16_t, IntegralExpression> {
std::unordered_map<uint16_t, IntegralExpression> ret;
try {
for (const auto& it : json->get_dict(key)) {
if (!it.first.starts_with("F_")) {
throw runtime_error("invalid flag reference: " + it.first);
}
uint16_t flag = stoul(it.first.substr(2), nullptr, 16);
if (it.second->is_bool()) {
ret.emplace(flag, it.second->as_bool() ? "true" : "false");
} else {
ret.emplace(flag, it.second->as_string());
}
}
} catch (const out_of_range&) {
}
} catch (const out_of_range&) {
}
return ret;
};
this->quest_flag_rewrites_v1_v2 = parse_quest_flag_rewrites("QuestFlagRewritesV1V2");
this->quest_flag_rewrites_v3 = parse_quest_flag_rewrites("QuestFlagRewritesV3");
this->quest_flag_rewrites_v4 = parse_quest_flag_rewrites("QuestFlagRewritesV4");
this->persistent_game_idle_timeout_usecs = this->config_json->get_int("PersistentGameIdleTimeout", 0);
this->cheat_mode_behavior = parse_behavior_switch("CheatModeBehavior", BehaviorSwitch::OFF_BY_DEFAULT);
@@ -890,7 +904,6 @@ void ServerState::load_config_early() {
this->allow_dc_pc_games = this->config_json->get_bool("AllowDCPCGames", true);
this->allow_gc_xb_games = this->config_json->get_bool("AllowGCXBGames", true);
this->enable_chat_commands = this->config_json->get_bool("EnableChatCommands", true);
this->unlock_all_areas = this->config_json->get_bool("UnlockAllAreas", false);
this->version_name_colors.reset();
try {
+3 -2
View File
@@ -91,7 +91,6 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
bool allow_dc_pc_games = true;
bool allow_gc_xb_games = true;
bool enable_chat_commands = true;
bool unlock_all_areas = false;
std::unique_ptr<std::array<uint32_t, NUM_NON_PATCH_VERSIONS>> version_name_colors;
uint8_t allowed_drop_modes_v1_v2_normal = 0x1F;
uint8_t allowed_drop_modes_v1_v2_battle = 0x07;
@@ -111,7 +110,9 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
Lobby::DropMode default_drop_mode_v4_normal = Lobby::DropMode::SERVER_SHARED;
Lobby::DropMode default_drop_mode_v4_battle = Lobby::DropMode::SERVER_SHARED;
Lobby::DropMode default_drop_mode_v4_challenge = Lobby::DropMode::SERVER_SHARED;
QuestFlagsForDifficulty quest_flag_persist_mask;
std::unordered_map<uint16_t, IntegralExpression> quest_flag_rewrites_v1_v2;
std::unordered_map<uint16_t, IntegralExpression> quest_flag_rewrites_v3;
std::unordered_map<uint16_t, IntegralExpression> quest_flag_rewrites_v4;
uint64_t persistent_game_idle_timeout_usecs = 0;
bool ep3_send_function_call_enabled = false;
bool enable_v3_v4_protected_subcommands = false;
+49 -14
View File
@@ -958,14 +958,6 @@
// available on the proxy server.
"CheatModeBehavior": "OnByDefault",
// Whether to unlock all areas by default in Ep1/2/4 games. If this is on,
// the Ragol warp in Pioneer 2 will allow access to all base areas (Forest 1,
// Cave 1, Mine 1, and Ruins 1 in Episode 1, for example) even if the player
// who created the game does not yet have access to those areas.
// Note that some late PSOBB client versions (for example, the Tethealla
// client) open all areas by default, so this setting has no effect for them.
"UnlockAllAreas": false,
// Whether to enable rare drop notifications by default. Players can toggle
// this behavior for themselves with the $itemnotifs command.
"RareNotificationsEnabledByDefaultV1V2": false,
@@ -1107,12 +1099,55 @@
"Episode2": [1, 30, 60, 100],
"Episode4": [1, 40, 70, 110],
},
// Some quest flags should not be written to the character file when they're
// updated during a quest. Specify the flag IDs here to prevent those flags
// from being updated in saved BB character data. The default value here
// prevents the door locks in CCA from being deactivated in free-roam by
// loading a quest.
"PreventPersistQuestFlags": [0x0046, 0x0047, 0x0048],
// Some quest flags should be changed when a game is started in order to fix
// certain in-game issues (noted in the comments in the default values below).
// These can be true, false, or expressions to make values conditional on
// other flags' values. For non-BB versions, you should generally only use
// true and false here, since the server doesn't have direct access to the
// client's quest flags from their save file.
// If you use an expression, the format is the same as the AvailableIf and
// EnabledIf fields in quest JSON files (see system/quests/battle/b88001.json
// for details). Note that the expression is only evaluated at the time the
// game is created, and the player-specific tokens like C_EpX_YY refer to the
// player who created the game.
// The UnlockAllAreas option is now gone; if you want the same behavior as if
// it were enabled, uncomment all the "area unlocks" lines below. Note that
// some late PSOBB client versions (for example, the Tethealla client) open
// all areas by default, so the area unlock flags have no effect for them.
"QuestFlagRewritesV1V2": {
// "F_0017": true, // Ep1 area unlocks
// "F_0020": true, // Ep1 area unlocks
// "F_002A": true, // Ep1 area unlocks
},
"QuestFlagRewritesV3": {
// "F_0017": true, // Ep1 area unlocks
// "F_0020": true, // Ep1 area unlocks
// "F_002A": true, // Ep1 area unlocks
// "F_004C": true, // Ep2 area unlocks
// "F_004F": true, // Ep2 area unlocks
// "F_0052": true, // Ep2 area unlocks
},
"QuestFlagRewritesV4": {
// "F_01F9": true, // Ep1 area unlocks
// "F_0201": true, // Ep1 area unlocks
// "F_0207": true, // Ep1 area unlocks
// "F_021B": true, // Ep2 area unlocks
// "F_0225": true, // Ep2 area unlocks
// "F_022F": true, // Ep2 area unlocks
// "F_02BD": true, // Ep4 area unlocks
// "F_02BE": true, // Ep4 area unlocks
// "F_02BF": true, // Ep4 area unlocks
// "F_02C0": true, // Ep4 area unlocks
// "F_02C1": true, // Ep4 area unlocks
"F_0046": false, // Ep2 CCA door lock fix
"F_0047": false, // Ep2 CCA door lock fix
"F_0048": false, // Ep2 CCA door lock fix
"F_002C": "F_01F7", // Ep1 Forest monument state = 1-2 cleared
"F_002D": "F_01FD", // Ep1 Cave monument state = 2-2 cleared
"F_002E": "F_0209", // Ep1 Mine monument state = 4-1 cleared
"F_002F": "F_01F7 && F_01FD && F_0209", // All monuments state
},
// Whether to enable certain exception handling. Disabling this causes
// newserv to abort when any client causes an exception, which is generally
+34 -2
View File
@@ -30,7 +30,6 @@
"DefaultDropModeV4Battle": "SERVER_SHARED",
"DefaultDropModeV4Challenge": "SERVER_SHARED",
"CheatModeBehavior": "OnByDefault",
"UnlockAllAreas": false,
"RareNotificationsEnabledByDefault": false,
"NotifyGameForItemPrimaryIdentifiers": [],
"NotifyServerForItemPrimaryIdentifiers": [],
@@ -407,5 +406,38 @@
"Episode2": [1, 30, 60, 100],
"Episode4": [1, 40, 70, 110],
},
"PreventPersistQuestFlags": [0x0046, 0x0047, 0x0048],
"QuestFlagRewritesV1V2": {
// "F_0017": true, // Ep1 area unlocks
// "F_0020": true, // Ep1 area unlocks
// "F_002A": true, // Ep1 area unlocks
},
"QuestFlagRewritesV3": {
// "F_0017": true, // Ep1 area unlocks
// "F_0020": true, // Ep1 area unlocks
// "F_002A": true, // Ep1 area unlocks
// "F_004C": true, // Ep2 area unlocks
// "F_004F": true, // Ep2 area unlocks
// "F_0052": true, // Ep2 area unlocks
},
"QuestFlagRewritesV4": {
// "F_01F9": true, // Ep1 area unlocks
// "F_0201": true, // Ep1 area unlocks
// "F_0207": true, // Ep1 area unlocks
// "F_021B": true, // Ep2 area unlocks
// "F_0225": true, // Ep2 area unlocks
// "F_022F": true, // Ep2 area unlocks
// "F_02BD": true, // Ep4 area unlocks
// "F_02BE": true, // Ep4 area unlocks
// "F_02BF": true, // Ep4 area unlocks
// "F_02C0": true, // Ep4 area unlocks
// "F_02C1": true, // Ep4 area unlocks
"F_0046": false, // Ep2 CCA door lock fix
"F_0047": false, // Ep2 CCA door lock fix
"F_0048": false, // Ep2 CCA door lock fix
"F_002C": "F_01F7", // Ep1 Forest monument state = 1-2 cleared
"F_002D": "F_01FD", // Ep1 Cave monument state = 2-2 cleared
"F_002E": "F_0209", // Ep1 Mine monument state = 4-1 cleared
"F_002F": "F_01F7 && F_01FD && F_0209", // All monuments state
},
}