add ability to hide quests based on game player count
This commit is contained in:
+15
-5
@@ -295,7 +295,7 @@ shared_ptr<const TeamIndex::Team> Client::team() const {
|
|||||||
return team;
|
return team;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Client::can_see_quest(shared_ptr<const Quest> q, uint8_t difficulty) const {
|
bool Client::can_see_quest(shared_ptr<const Quest> q, uint8_t difficulty, size_t num_players) 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;
|
||||||
}
|
}
|
||||||
@@ -303,12 +303,17 @@ bool Client::can_see_quest(shared_ptr<const Quest> q, uint8_t difficulty) const
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
string expr = q->available_expression->str();
|
string expr = q->available_expression->str();
|
||||||
bool ret = q->available_expression->evaluate(this->character()->quest_flags.data.at(difficulty), this->team());
|
QuestAvailabilityExpression::Env env = {
|
||||||
this->log.info("Evaluating quest availability expression %s => %s", expr.c_str(), ret ? "TRUE" : "FALSE");
|
.flags = &this->character()->quest_flags.data.at(difficulty),
|
||||||
|
.team = this->team(),
|
||||||
|
.num_players = num_players,
|
||||||
|
};
|
||||||
|
int64_t ret = q->available_expression->evaluate(env);
|
||||||
|
this->log.info("Evaluated quest availability expression %s => %s", expr.c_str(), ret ? "TRUE" : "FALSE");
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Client::can_play_quest(shared_ptr<const Quest> q, uint8_t difficulty) const {
|
bool Client::can_play_quest(shared_ptr<const Quest> q, uint8_t difficulty, size_t num_players) 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;
|
||||||
}
|
}
|
||||||
@@ -316,7 +321,12 @@ bool Client::can_play_quest(shared_ptr<const Quest> q, uint8_t difficulty) const
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
string expr = q->enabled_expression->str();
|
string expr = q->enabled_expression->str();
|
||||||
bool ret = q->enabled_expression->evaluate(this->character()->quest_flags.data.at(difficulty), this->team());
|
QuestAvailabilityExpression::Env env = {
|
||||||
|
.flags = &this->character()->quest_flags.data.at(difficulty),
|
||||||
|
.team = this->team(),
|
||||||
|
.num_players = num_players,
|
||||||
|
};
|
||||||
|
bool ret = q->enabled_expression->evaluate(env);
|
||||||
this->log.info("Evaluating quest enabled expression %s => %s", expr.c_str(), ret ? "TRUE" : "FALSE");
|
this->log.info("Evaluating quest enabled expression %s => %s", expr.c_str(), ret ? "TRUE" : "FALSE");
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -261,8 +261,8 @@ public:
|
|||||||
|
|
||||||
std::shared_ptr<const TeamIndex::Team> team() const;
|
std::shared_ptr<const TeamIndex::Team> team() const;
|
||||||
|
|
||||||
bool can_see_quest(std::shared_ptr<const Quest> q, uint8_t difficulty) const;
|
bool can_see_quest(std::shared_ptr<const Quest> q, uint8_t difficulty, size_t num_players) const;
|
||||||
bool can_play_quest(std::shared_ptr<const Quest> q, uint8_t difficulty) const;
|
bool can_play_quest(std::shared_ptr<const Quest> q, uint8_t difficulty, size_t num_players) 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();
|
||||||
|
|||||||
+4
-3
@@ -736,13 +736,14 @@ 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) -> QuestIndex::IncludeState {
|
size_t num_players = this->count_clients();
|
||||||
|
return [this, num_players](shared_ptr<const Quest> q) -> QuestIndex::IncludeState {
|
||||||
bool is_enabled = true;
|
bool is_enabled = true;
|
||||||
for (const auto& lc : this->clients) {
|
for (const auto& lc : this->clients) {
|
||||||
if (lc && !lc->can_see_quest(q, this->difficulty)) {
|
if (lc && !lc->can_see_quest(q, this->difficulty, num_players)) {
|
||||||
return QuestIndex::IncludeState::HIDDEN;
|
return QuestIndex::IncludeState::HIDDEN;
|
||||||
}
|
}
|
||||||
if (lc && !lc->can_play_quest(q, this->difficulty)) {
|
if (lc && !lc->can_play_quest(q, this->difficulty, num_players)) {
|
||||||
is_enabled = false;
|
is_enabled = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,69 +24,144 @@ using namespace std;
|
|||||||
QuestAvailabilityExpression::QuestAvailabilityExpression(const string& text)
|
QuestAvailabilityExpression::QuestAvailabilityExpression(const string& text)
|
||||||
: root(this->parse_expr(text)) {}
|
: root(this->parse_expr(text)) {}
|
||||||
|
|
||||||
QuestAvailabilityExpression::OrNode::OrNode(unique_ptr<const Node>&& left, unique_ptr<const Node>&& right)
|
QuestAvailabilityExpression::BinaryOperatorNode::BinaryOperatorNode(
|
||||||
: left(std::move(left)),
|
Type type, unique_ptr<const Node>&& left, unique_ptr<const Node>&& right)
|
||||||
|
: type(type),
|
||||||
|
left(std::move(left)),
|
||||||
right(std::move(right)) {}
|
right(std::move(right)) {}
|
||||||
|
|
||||||
bool QuestAvailabilityExpression::OrNode::operator==(const Node& other) const {
|
bool QuestAvailabilityExpression::BinaryOperatorNode::operator==(const Node& other) const {
|
||||||
try {
|
try {
|
||||||
const OrNode& other_or = dynamic_cast<const OrNode&>(other);
|
const BinaryOperatorNode& other_bin = dynamic_cast<const BinaryOperatorNode&>(other);
|
||||||
return *other_or.left == *this->left && *other_or.right == *this->right;
|
return other_bin.type == this->type && *other_bin.left == *this->left && *other_bin.right == *this->right;
|
||||||
} catch (const bad_cast&) {
|
} catch (const bad_cast&) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QuestAvailabilityExpression::OrNode::evaluate(
|
int64_t QuestAvailabilityExpression::BinaryOperatorNode::evaluate(const Env& env) const {
|
||||||
const QuestFlagsForDifficulty& flags, shared_ptr<const TeamIndex::Team> team) const {
|
switch (this->type) {
|
||||||
return this->left->evaluate(flags, team) || this->right->evaluate(flags, team);
|
case Type::LOGICAL_OR:
|
||||||
|
return this->left->evaluate(env) || this->right->evaluate(env);
|
||||||
|
case Type::LOGICAL_AND:
|
||||||
|
return this->left->evaluate(env) && this->right->evaluate(env);
|
||||||
|
case Type::BITWISE_OR:
|
||||||
|
return this->left->evaluate(env) | this->right->evaluate(env);
|
||||||
|
case Type::BITWISE_AND:
|
||||||
|
return this->left->evaluate(env) & this->right->evaluate(env);
|
||||||
|
case Type::BITWISE_XOR:
|
||||||
|
return this->left->evaluate(env) ^ this->right->evaluate(env);
|
||||||
|
case Type::LEFT_SHIFT:
|
||||||
|
return this->left->evaluate(env) << this->right->evaluate(env);
|
||||||
|
case Type::RIGHT_SHIFT:
|
||||||
|
return this->left->evaluate(env) >> this->right->evaluate(env);
|
||||||
|
case Type::LESS_THAN:
|
||||||
|
return this->left->evaluate(env) < this->right->evaluate(env);
|
||||||
|
case Type::GREATER_THAN:
|
||||||
|
return this->left->evaluate(env) > this->right->evaluate(env);
|
||||||
|
case Type::LESS_OR_EQUAL:
|
||||||
|
return this->left->evaluate(env) <= this->right->evaluate(env);
|
||||||
|
case Type::GREATER_OR_EQUAL:
|
||||||
|
return this->left->evaluate(env) >= this->right->evaluate(env);
|
||||||
|
case Type::EQUAL:
|
||||||
|
return this->left->evaluate(env) == this->right->evaluate(env);
|
||||||
|
case Type::NOT_EQUAL:
|
||||||
|
return this->left->evaluate(env) != this->right->evaluate(env);
|
||||||
|
case Type::ADD:
|
||||||
|
return this->left->evaluate(env) + this->right->evaluate(env);
|
||||||
|
case Type::SUBTRACT:
|
||||||
|
return this->left->evaluate(env) - this->right->evaluate(env);
|
||||||
|
case Type::MULTIPLY:
|
||||||
|
return this->left->evaluate(env) * this->right->evaluate(env);
|
||||||
|
case Type::DIVIDE:
|
||||||
|
return this->left->evaluate(env) / this->right->evaluate(env);
|
||||||
|
case Type::MODULUS:
|
||||||
|
return this->left->evaluate(env) % this->right->evaluate(env);
|
||||||
|
default:
|
||||||
|
throw logic_error("invalid binary operator type");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string QuestAvailabilityExpression::OrNode::str() const {
|
string QuestAvailabilityExpression::BinaryOperatorNode::str() const {
|
||||||
|
switch (this->type) {
|
||||||
|
case Type::LOGICAL_OR:
|
||||||
return "(" + this->left->str() + ") || (" + this->right->str() + ")";
|
return "(" + this->left->str() + ") || (" + this->right->str() + ")";
|
||||||
}
|
case Type::LOGICAL_AND:
|
||||||
|
|
||||||
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() + ")";
|
return "(" + this->left->str() + ") && (" + this->right->str() + ")";
|
||||||
|
case Type::BITWISE_OR:
|
||||||
|
return "(" + this->left->str() + ") | (" + this->right->str() + ")";
|
||||||
|
case Type::BITWISE_AND:
|
||||||
|
return "(" + this->left->str() + ") & (" + this->right->str() + ")";
|
||||||
|
case Type::BITWISE_XOR:
|
||||||
|
return "(" + this->left->str() + ") ^ (" + this->right->str() + ")";
|
||||||
|
case Type::LEFT_SHIFT:
|
||||||
|
return "(" + this->left->str() + ") << (" + this->right->str() + ")";
|
||||||
|
case Type::RIGHT_SHIFT:
|
||||||
|
return "(" + this->left->str() + ") >> (" + this->right->str() + ")";
|
||||||
|
case Type::LESS_THAN:
|
||||||
|
return "(" + this->left->str() + ") < (" + this->right->str() + ")";
|
||||||
|
case Type::GREATER_THAN:
|
||||||
|
return "(" + this->left->str() + ") > (" + this->right->str() + ")";
|
||||||
|
case Type::LESS_OR_EQUAL:
|
||||||
|
return "(" + this->left->str() + ") <= (" + this->right->str() + ")";
|
||||||
|
case Type::GREATER_OR_EQUAL:
|
||||||
|
return "(" + this->left->str() + ") >= (" + this->right->str() + ")";
|
||||||
|
case Type::EQUAL:
|
||||||
|
return "(" + this->left->str() + ") == (" + this->right->str() + ")";
|
||||||
|
case Type::NOT_EQUAL:
|
||||||
|
return "(" + this->left->str() + ") != (" + this->right->str() + ")";
|
||||||
|
case Type::ADD:
|
||||||
|
return "(" + this->left->str() + ") + (" + this->right->str() + ")";
|
||||||
|
case Type::SUBTRACT:
|
||||||
|
return "(" + this->left->str() + ") - (" + this->right->str() + ")";
|
||||||
|
case Type::MULTIPLY:
|
||||||
|
return "(" + this->left->str() + ") * (" + this->right->str() + ")";
|
||||||
|
case Type::DIVIDE:
|
||||||
|
return "(" + this->left->str() + ") / (" + this->right->str() + ")";
|
||||||
|
case Type::MODULUS:
|
||||||
|
return "(" + this->left->str() + ") % (" + this->right->str() + ")";
|
||||||
|
default:
|
||||||
|
throw logic_error("invalid binary operator type");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QuestAvailabilityExpression::NotNode::NotNode(unique_ptr<const Node>&& sub)
|
QuestAvailabilityExpression::UnaryOperatorNode::UnaryOperatorNode(Type type, unique_ptr<const Node>&& sub)
|
||||||
: sub(std::move(sub)) {}
|
: type(type),
|
||||||
|
sub(std::move(sub)) {}
|
||||||
|
|
||||||
bool QuestAvailabilityExpression::NotNode::operator==(const Node& other) const {
|
bool QuestAvailabilityExpression::UnaryOperatorNode::operator==(const Node& other) const {
|
||||||
try {
|
try {
|
||||||
const NotNode& other_not = dynamic_cast<const NotNode&>(other);
|
const UnaryOperatorNode& other_un = dynamic_cast<const UnaryOperatorNode&>(other);
|
||||||
return *other_not.sub == *this->sub;
|
return other_un.type == this->type && *other_un.sub == *this->sub;
|
||||||
} catch (const bad_cast&) {
|
} catch (const bad_cast&) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QuestAvailabilityExpression::NotNode::evaluate(
|
int64_t QuestAvailabilityExpression::UnaryOperatorNode::evaluate(const Env& env) const {
|
||||||
const QuestFlagsForDifficulty& flags, shared_ptr<const TeamIndex::Team> team) const {
|
switch (this->type) {
|
||||||
return !this->sub->evaluate(flags, team);
|
case Type::LOGICAL_NOT:
|
||||||
|
return !this->sub->evaluate(env);
|
||||||
|
case Type::BITWISE_NOT:
|
||||||
|
return ~this->sub->evaluate(env);
|
||||||
|
case Type::NEGATIVE:
|
||||||
|
return -this->sub->evaluate(env);
|
||||||
|
default:
|
||||||
|
throw logic_error("invalid unary operator type");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string QuestAvailabilityExpression::NotNode::str() const {
|
string QuestAvailabilityExpression::UnaryOperatorNode::str() const {
|
||||||
|
switch (this->type) {
|
||||||
|
case Type::LOGICAL_NOT:
|
||||||
return "!(" + this->sub->str() + ")";
|
return "!(" + this->sub->str() + ")";
|
||||||
|
case Type::BITWISE_NOT:
|
||||||
|
return "~(" + this->sub->str() + ")";
|
||||||
|
case Type::NEGATIVE:
|
||||||
|
return "-(" + this->sub->str() + ")";
|
||||||
|
default:
|
||||||
|
throw logic_error("invalid unary operator type");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QuestAvailabilityExpression::FlagLookupNode::FlagLookupNode(uint16_t flag_index)
|
QuestAvailabilityExpression::FlagLookupNode::FlagLookupNode(uint16_t flag_index)
|
||||||
@@ -101,9 +176,8 @@ bool QuestAvailabilityExpression::FlagLookupNode::operator==(const Node& other)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QuestAvailabilityExpression::FlagLookupNode::evaluate(
|
int64_t QuestAvailabilityExpression::FlagLookupNode::evaluate(const Env& env) const {
|
||||||
const QuestFlagsForDifficulty& flags, shared_ptr<const TeamIndex::Team>) const {
|
return env.flags->get(this->flag_index) ? 1 : 0;
|
||||||
return flags.get(this->flag_index);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
string QuestAvailabilityExpression::FlagLookupNode::str() const {
|
string QuestAvailabilityExpression::FlagLookupNode::str() const {
|
||||||
@@ -122,15 +196,28 @@ bool QuestAvailabilityExpression::TeamRewardLookupNode::operator==(const Node& o
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QuestAvailabilityExpression::TeamRewardLookupNode::evaluate(
|
int64_t QuestAvailabilityExpression::TeamRewardLookupNode::evaluate(const Env& env) const {
|
||||||
const QuestFlagsForDifficulty&, shared_ptr<const TeamIndex::Team> team) const {
|
return (env.team && env.team->has_reward(this->reward_name)) ? 1 : 0;
|
||||||
return team && team->has_reward(this->reward_name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
string QuestAvailabilityExpression::TeamRewardLookupNode::str() const {
|
string QuestAvailabilityExpression::TeamRewardLookupNode::str() const {
|
||||||
return "T_" + this->reward_name;
|
return "T_" + this->reward_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QuestAvailabilityExpression::NumPlayersLookupNode::NumPlayersLookupNode() {}
|
||||||
|
|
||||||
|
bool QuestAvailabilityExpression::NumPlayersLookupNode::operator==(const Node& other) const {
|
||||||
|
return dynamic_cast<const NumPlayersLookupNode*>(&other) != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t QuestAvailabilityExpression::NumPlayersLookupNode::evaluate(const Env& env) const {
|
||||||
|
return env.num_players;
|
||||||
|
}
|
||||||
|
|
||||||
|
string QuestAvailabilityExpression::NumPlayersLookupNode::str() const {
|
||||||
|
return "V_NumPlayers";
|
||||||
|
}
|
||||||
|
|
||||||
QuestAvailabilityExpression::ConstantNode::ConstantNode(bool value)
|
QuestAvailabilityExpression::ConstantNode::ConstantNode(bool value)
|
||||||
: value(value) {}
|
: value(value) {}
|
||||||
|
|
||||||
@@ -143,13 +230,12 @@ bool QuestAvailabilityExpression::ConstantNode::operator==(const Node& other) co
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool QuestAvailabilityExpression::ConstantNode::evaluate(
|
int64_t QuestAvailabilityExpression::ConstantNode::evaluate(const Env&) const {
|
||||||
const QuestFlagsForDifficulty&, shared_ptr<const TeamIndex::Team>) const {
|
|
||||||
return this->value;
|
return this->value;
|
||||||
}
|
}
|
||||||
|
|
||||||
string QuestAvailabilityExpression::ConstantNode::str() const {
|
string QuestAvailabilityExpression::ConstantNode::str() const {
|
||||||
return this->value ? "true" : "false";
|
return string_printf("%" PRId64, this->value);
|
||||||
}
|
}
|
||||||
|
|
||||||
unique_ptr<const QuestAvailabilityExpression::Node> QuestAvailabilityExpression::parse_expr(string_view text) {
|
unique_ptr<const QuestAvailabilityExpression::Node> QuestAvailabilityExpression::parse_expr(string_view text) {
|
||||||
@@ -186,48 +272,66 @@ unique_ptr<const QuestAvailabilityExpression::Node> QuestAvailabilityExpression:
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (text.empty()) {
|
||||||
|
throw runtime_error("invalid expression");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for unary operators
|
||||||
|
if (text[0] == '!') {
|
||||||
|
return make_unique<UnaryOperatorNode>(UnaryOperatorNode::Type::LOGICAL_NOT,
|
||||||
|
QuestAvailabilityExpression::parse_expr(text.substr(1)));
|
||||||
|
} else if (text[0] == '~') {
|
||||||
|
return make_unique<UnaryOperatorNode>(UnaryOperatorNode::Type::BITWISE_NOT,
|
||||||
|
QuestAvailabilityExpression::parse_expr(text.substr(1)));
|
||||||
|
} else if (text[0] == '-') {
|
||||||
|
return make_unique<UnaryOperatorNode>(UnaryOperatorNode::Type::NEGATIVE,
|
||||||
|
QuestAvailabilityExpression::parse_expr(text.substr(1)));
|
||||||
|
}
|
||||||
|
|
||||||
// Check for binary operators at the root level
|
// Check for binary operators at the root level
|
||||||
|
using BinType = BinaryOperatorNode::Type;
|
||||||
|
static const vector<vector<pair<std::string, BinaryOperatorNode::Type>>> binary_operator_levels = {
|
||||||
|
{{make_pair("*", BinType::MULTIPLY)}, {make_pair("/", BinType::DIVIDE)}, {make_pair("%", BinType::MODULUS)}},
|
||||||
|
{{make_pair("+", BinType::ADD)}, {make_pair("-", BinType::SUBTRACT)}},
|
||||||
|
{{make_pair("<<", BinType::LEFT_SHIFT)}, {make_pair(">>", BinType::RIGHT_SHIFT)}},
|
||||||
|
{{make_pair("<=", BinType::LESS_OR_EQUAL)}, {make_pair(">=", BinType::GREATER_OR_EQUAL)}, {make_pair("<", BinType::LESS_THAN)}, {make_pair(">", BinType::GREATER_THAN)}},
|
||||||
|
{{make_pair("==", BinType::EQUAL)}, {make_pair("!=", BinType::NOT_EQUAL)}},
|
||||||
|
{{make_pair("&", BinType::BITWISE_AND)}},
|
||||||
|
{{make_pair("^", BinType::BITWISE_XOR)}},
|
||||||
|
{{make_pair("|", BinType::BITWISE_OR)}},
|
||||||
|
{{make_pair("&&", BinType::LOGICAL_AND)}},
|
||||||
|
{{make_pair("||", BinType::LOGICAL_OR)}},
|
||||||
|
};
|
||||||
|
for (const auto& operators : binary_operator_levels) {
|
||||||
size_t paren_level = 0;
|
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++) {
|
for (size_t z = 0; z < text.size() - 1; z++) {
|
||||||
if (text[z] == '(') {
|
if (text[z] == '(') {
|
||||||
paren_level++;
|
paren_level++;
|
||||||
|
continue;
|
||||||
} else if (text[z] == ')') {
|
} else if (text[z] == ')') {
|
||||||
paren_level--;
|
paren_level--;
|
||||||
} else if ((text[z] == '&') && (text[z + 1] == '&') && !paren_level) {
|
continue;
|
||||||
and_pos = z;
|
}
|
||||||
} else if ((text[z] == '|') && (text[z + 1] == '|') && !paren_level) {
|
if (!paren_level) {
|
||||||
or_pos = z;
|
for (const auto& oper : operators) {
|
||||||
|
// Awful hack (because I'm too lazy to add a tokenization step): if
|
||||||
|
// the operator is followed or preceded by another copy of itself,
|
||||||
|
// don't match it (this prevents us from matching & when the token is
|
||||||
|
// actually &&)
|
||||||
|
if ((text.size() > z + oper.first.size()) &&
|
||||||
|
((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()));
|
||||||
|
return make_unique<BinaryOperatorNode>(oper.second, std::move(left), std::move(right));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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
|
// Check for env lookups
|
||||||
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_")) {
|
if (text.starts_with("F_")) {
|
||||||
char* endptr = nullptr;
|
char* endptr = nullptr;
|
||||||
uint64_t flag = strtoul(text.data() + 2, &endptr, 16);
|
uint64_t flag = strtoul(text.data() + 2, &endptr, 16);
|
||||||
@@ -239,10 +343,27 @@ unique_ptr<const QuestAvailabilityExpression::Node> QuestAvailabilityExpression:
|
|||||||
}
|
}
|
||||||
return make_unique<FlagLookupNode>(flag);
|
return make_unique<FlagLookupNode>(flag);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (text.starts_with("T_")) {
|
if (text.starts_with("T_")) {
|
||||||
return make_unique<TeamRewardLookupNode>(string(text.substr(2)));
|
return make_unique<TeamRewardLookupNode>(string(text.substr(2)));
|
||||||
}
|
}
|
||||||
|
if (text == "V_NumPlayers") {
|
||||||
|
return make_unique<NumPlayersLookupNode>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for constants
|
||||||
|
if (text == "true") {
|
||||||
|
return make_unique<ConstantNode>(1);
|
||||||
|
}
|
||||||
|
if (text == "false") {
|
||||||
|
return make_unique<ConstantNode>(0);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
size_t endpos;
|
||||||
|
int64_t v = stoll(string(text), &endpos, 0);
|
||||||
|
if (endpos == text.size()) {
|
||||||
|
return make_unique<ConstantNode>(v);
|
||||||
|
}
|
||||||
|
} catch (const exception&) {
|
||||||
|
}
|
||||||
throw runtime_error("unparseable expression");
|
throw runtime_error("unparseable expression");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,12 @@
|
|||||||
|
|
||||||
class QuestAvailabilityExpression {
|
class QuestAvailabilityExpression {
|
||||||
public:
|
public:
|
||||||
|
struct Env {
|
||||||
|
const QuestFlagsForDifficulty* flags;
|
||||||
|
std::shared_ptr<const TeamIndex::Team> team;
|
||||||
|
size_t num_players;
|
||||||
|
};
|
||||||
|
|
||||||
QuestAvailabilityExpression(const std::string& text);
|
QuestAvailabilityExpression(const std::string& text);
|
||||||
~QuestAvailabilityExpression() = default;
|
~QuestAvailabilityExpression() = default;
|
||||||
inline bool operator==(const QuestAvailabilityExpression& other) const {
|
inline bool operator==(const QuestAvailabilityExpression& other) const {
|
||||||
@@ -23,8 +29,8 @@ public:
|
|||||||
inline bool operator!=(const QuestAvailabilityExpression& other) const {
|
inline bool operator!=(const QuestAvailabilityExpression& other) const {
|
||||||
return !this->operator==(other);
|
return !this->operator==(other);
|
||||||
}
|
}
|
||||||
inline bool evaluate(const QuestFlagsForDifficulty& flags, std::shared_ptr<const TeamIndex::Team> team) const {
|
inline int64_t evaluate(const Env& env) const {
|
||||||
return this->root->evaluate(flags, team);
|
return this->root->evaluate(env);
|
||||||
}
|
}
|
||||||
inline std::string str() const {
|
inline std::string str() const {
|
||||||
return this->root->str();
|
return this->root->str();
|
||||||
@@ -38,48 +44,62 @@ protected:
|
|||||||
inline bool operator!=(const Node& other) const {
|
inline bool operator!=(const Node& other) const {
|
||||||
return !this->operator==(other);
|
return !this->operator==(other);
|
||||||
}
|
}
|
||||||
virtual bool evaluate(const QuestFlagsForDifficulty& flags, std::shared_ptr<const TeamIndex::Team> team) const = 0;
|
virtual int64_t evaluate(const Env& env) const = 0;
|
||||||
virtual std::string str() const = 0;
|
virtual std::string str() const = 0;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Node() = default;
|
Node() = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
class OrNode : public Node {
|
class BinaryOperatorNode : public Node {
|
||||||
public:
|
public:
|
||||||
OrNode(std::unique_ptr<const Node>&& left, std::unique_ptr<const Node>&& right);
|
enum class Type {
|
||||||
virtual ~OrNode() = default;
|
LOGICAL_OR = 0,
|
||||||
|
LOGICAL_AND,
|
||||||
|
BITWISE_OR,
|
||||||
|
BITWISE_AND,
|
||||||
|
BITWISE_XOR,
|
||||||
|
LEFT_SHIFT,
|
||||||
|
RIGHT_SHIFT,
|
||||||
|
LESS_THAN,
|
||||||
|
GREATER_THAN,
|
||||||
|
LESS_OR_EQUAL,
|
||||||
|
GREATER_OR_EQUAL,
|
||||||
|
EQUAL,
|
||||||
|
NOT_EQUAL,
|
||||||
|
ADD,
|
||||||
|
SUBTRACT,
|
||||||
|
MULTIPLY,
|
||||||
|
DIVIDE,
|
||||||
|
MODULUS,
|
||||||
|
};
|
||||||
|
BinaryOperatorNode(Type type, std::unique_ptr<const Node>&& left, std::unique_ptr<const Node>&& right);
|
||||||
|
virtual ~BinaryOperatorNode() = default;
|
||||||
virtual bool operator==(const Node& other) const;
|
virtual bool operator==(const Node& other) const;
|
||||||
virtual bool evaluate(const QuestFlagsForDifficulty& flags, std::shared_ptr<const TeamIndex::Team> team) const;
|
virtual int64_t evaluate(const Env& env) const;
|
||||||
virtual std::string str() const;
|
virtual std::string str() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
Type type;
|
||||||
std::unique_ptr<const Node> left;
|
std::unique_ptr<const Node> left;
|
||||||
std::unique_ptr<const Node> right;
|
std::unique_ptr<const Node> right;
|
||||||
};
|
};
|
||||||
|
|
||||||
class AndNode : public Node {
|
class UnaryOperatorNode : public Node {
|
||||||
public:
|
public:
|
||||||
AndNode(std::unique_ptr<const Node>&& left, std::unique_ptr<const Node>&& right);
|
enum class Type {
|
||||||
virtual ~AndNode() = default;
|
LOGICAL_NOT = 0,
|
||||||
virtual bool operator==(const Node& other) const;
|
BITWISE_NOT,
|
||||||
virtual bool evaluate(const QuestFlagsForDifficulty& flags, std::shared_ptr<const TeamIndex::Team> team) const;
|
NEGATIVE,
|
||||||
virtual std::string str() const;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
std::unique_ptr<const Node> left;
|
|
||||||
std::unique_ptr<const Node> right;
|
|
||||||
};
|
};
|
||||||
|
UnaryOperatorNode(Type type, std::unique_ptr<const Node>&& sub);
|
||||||
class NotNode : public Node {
|
virtual ~UnaryOperatorNode() = default;
|
||||||
public:
|
|
||||||
NotNode(std::unique_ptr<const Node>&& sub);
|
|
||||||
virtual ~NotNode() = default;
|
|
||||||
virtual bool operator==(const Node& other) const;
|
virtual bool operator==(const Node& other) const;
|
||||||
virtual bool evaluate(const QuestFlagsForDifficulty& flags, std::shared_ptr<const TeamIndex::Team> team) const;
|
virtual int64_t evaluate(const Env& env) const;
|
||||||
virtual std::string str() const;
|
virtual std::string str() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
Type type;
|
||||||
std::unique_ptr<const Node> sub;
|
std::unique_ptr<const Node> sub;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -88,7 +108,7 @@ protected:
|
|||||||
FlagLookupNode(uint16_t flag_index);
|
FlagLookupNode(uint16_t flag_index);
|
||||||
virtual ~FlagLookupNode() = default;
|
virtual ~FlagLookupNode() = default;
|
||||||
virtual bool operator==(const Node& other) const;
|
virtual bool operator==(const Node& other) const;
|
||||||
virtual bool evaluate(const QuestFlagsForDifficulty& flags, std::shared_ptr<const TeamIndex::Team> team) const;
|
virtual int64_t evaluate(const Env& env) const;
|
||||||
virtual std::string str() const;
|
virtual std::string str() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@@ -100,23 +120,32 @@ protected:
|
|||||||
TeamRewardLookupNode(const std::string& reward_name);
|
TeamRewardLookupNode(const std::string& reward_name);
|
||||||
virtual ~TeamRewardLookupNode() = default;
|
virtual ~TeamRewardLookupNode() = default;
|
||||||
virtual bool operator==(const Node& other) const;
|
virtual bool operator==(const Node& other) const;
|
||||||
virtual bool evaluate(const QuestFlagsForDifficulty& flags, std::shared_ptr<const TeamIndex::Team> team) const;
|
virtual int64_t evaluate(const Env& env) const;
|
||||||
virtual std::string str() const;
|
virtual std::string str() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::string reward_name;
|
std::string reward_name;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class NumPlayersLookupNode : public Node {
|
||||||
|
public:
|
||||||
|
NumPlayersLookupNode();
|
||||||
|
virtual ~NumPlayersLookupNode() = default;
|
||||||
|
virtual bool operator==(const Node& other) const;
|
||||||
|
virtual int64_t evaluate(const Env& env) const;
|
||||||
|
virtual std::string str() const;
|
||||||
|
};
|
||||||
|
|
||||||
class ConstantNode : public Node {
|
class ConstantNode : public Node {
|
||||||
public:
|
public:
|
||||||
ConstantNode(bool value);
|
ConstantNode(bool value);
|
||||||
virtual ~ConstantNode() = default;
|
virtual ~ConstantNode() = default;
|
||||||
virtual bool operator==(const Node& other) const;
|
virtual bool operator==(const Node& other) const;
|
||||||
virtual bool evaluate(const QuestFlagsForDifficulty& flags, std::shared_ptr<const TeamIndex::Team> team) const;
|
virtual int64_t evaluate(const Env& env) const;
|
||||||
virtual std::string str() const;
|
virtual std::string str() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool value;
|
int64_t value;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::unique_ptr<const Node> parse_expr(std::string_view text);
|
std::unique_ptr<const Node> parse_expr(std::string_view text);
|
||||||
|
|||||||
@@ -2281,8 +2281,8 @@ 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_play_quest(game->quest, game->difficulty)) {
|
if (game->quest && !c->can_play_quest(game->quest, game->difficulty, game->count_clients() + 1)) {
|
||||||
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, or there\nis no space for another\nplayer in the quest.");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,10 +37,11 @@
|
|||||||
// 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 or a team reward has been purchased. To enable this feature, set a
|
// cleared or a team reward has been purchased. To enable this feature, set a
|
||||||
// value for AvailableIf in the quest's JSON file. This field's value should
|
// value for AvailableIf in the quest's JSON file. This field's value should
|
||||||
// be a boolean expression that tests one or more flags or team rewards. An
|
// be an integral expression that tests one or more flags or team rewards, or
|
||||||
// example with random values is shown below. This field is ignored if the
|
// the number of players. An example with random values is shown below. This
|
||||||
// player has the DISABLE_QUEST_REQUIREMENTS flag in their license.
|
// field is ignored if the player has the DISABLE_QUEST_REQUIREMENTS flag in
|
||||||
// "AvailableIf": "(F_016D || F_0171 || T_EpicCustomQuest) && !F_0173",
|
// their license.
|
||||||
|
// "AvailableIf": "(F_016D || T_EpicCustomQuest || (V_NumPlayers <= 2)) && !F_0173",
|
||||||
|
|
||||||
// On BB, quests may be disabled but still visible to the player. This
|
// On BB, quests may be disabled but still visible to the player. This
|
||||||
// expression controls when that should be the case. If AvailableIf evaluates
|
// expression controls when that should be the case. If AvailableIf evaluates
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"AvailableIf": "V_NumPlayers == 1",
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"AvailableIf": "V_NumPlayers == 1",
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"AvailableIf": "V_NumPlayers <= 2",
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"AvailableIf": "V_NumPlayers == 1",
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"AvailableIf": "V_NumPlayers == 1",
|
||||||
|
}
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"AvailableIf": "F_0073",
|
"AvailableIf": "F_0073 && (V_NumPlayers == 1)",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"AvailableIf": "V_NumPlayers == 1",
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"AvailableIf": "V_NumPlayers == 1",
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"AvailableIf": "V_NumPlayers == 1",
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user