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;
|
||||
}
|
||||
|
||||
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)) {
|
||||
return true;
|
||||
}
|
||||
@@ -303,12 +303,17 @@ bool Client::can_see_quest(shared_ptr<const Quest> q, uint8_t difficulty) const
|
||||
return true;
|
||||
}
|
||||
string expr = q->available_expression->str();
|
||||
bool ret = q->available_expression->evaluate(this->character()->quest_flags.data.at(difficulty), this->team());
|
||||
this->log.info("Evaluating quest availability expression %s => %s", expr.c_str(), ret ? "TRUE" : "FALSE");
|
||||
QuestAvailabilityExpression::Env env = {
|
||||
.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;
|
||||
}
|
||||
|
||||
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)) {
|
||||
return true;
|
||||
}
|
||||
@@ -316,7 +321,12 @@ bool Client::can_play_quest(shared_ptr<const Quest> q, uint8_t difficulty) const
|
||||
return true;
|
||||
}
|
||||
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");
|
||||
return ret;
|
||||
}
|
||||
|
||||
+2
-2
@@ -261,8 +261,8 @@ public:
|
||||
|
||||
std::shared_ptr<const TeamIndex::Team> team() const;
|
||||
|
||||
bool can_see_quest(std::shared_ptr<const Quest> q, uint8_t difficulty) const;
|
||||
bool can_play_quest(std::shared_ptr<const Quest> q, uint8_t difficulty) const;
|
||||
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, size_t num_players) const;
|
||||
|
||||
static void dispatch_save_game_data(evutil_socket_t, short, void* ctx);
|
||||
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 {
|
||||
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;
|
||||
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;
|
||||
}
|
||||
if (lc && !lc->can_play_quest(q, this->difficulty)) {
|
||||
if (lc && !lc->can_play_quest(q, this->difficulty, num_players)) {
|
||||
is_enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,69 +24,144 @@ using namespace std;
|
||||
QuestAvailabilityExpression::QuestAvailabilityExpression(const string& text)
|
||||
: root(this->parse_expr(text)) {}
|
||||
|
||||
QuestAvailabilityExpression::OrNode::OrNode(unique_ptr<const Node>&& left, unique_ptr<const Node>&& right)
|
||||
: left(std::move(left)),
|
||||
QuestAvailabilityExpression::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::OrNode::operator==(const Node& other) const {
|
||||
bool QuestAvailabilityExpression::BinaryOperatorNode::operator==(const Node& other) const {
|
||||
try {
|
||||
const OrNode& other_or = dynamic_cast<const OrNode&>(other);
|
||||
return *other_or.left == *this->left && *other_or.right == *this->right;
|
||||
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;
|
||||
} catch (const bad_cast&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool QuestAvailabilityExpression::OrNode::evaluate(
|
||||
const QuestFlagsForDifficulty& flags, shared_ptr<const TeamIndex::Team> team) const {
|
||||
return this->left->evaluate(flags, team) || this->right->evaluate(flags, team);
|
||||
int64_t QuestAvailabilityExpression::BinaryOperatorNode::evaluate(const Env& env) const {
|
||||
switch (this->type) {
|
||||
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 {
|
||||
return "(" + this->left->str() + ") || (" + this->right->str() + ")";
|
||||
string QuestAvailabilityExpression::BinaryOperatorNode::str() const {
|
||||
switch (this->type) {
|
||||
case Type::LOGICAL_OR:
|
||||
return "(" + this->left->str() + ") || (" + this->right->str() + ")";
|
||||
case Type::LOGICAL_AND:
|
||||
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::AndNode::AndNode(unique_ptr<const Node>&& left, unique_ptr<const Node>&& right)
|
||||
: left(std::move(left)),
|
||||
right(std::move(right)) {}
|
||||
QuestAvailabilityExpression::UnaryOperatorNode::UnaryOperatorNode(Type type, unique_ptr<const Node>&& sub)
|
||||
: type(type),
|
||||
sub(std::move(sub)) {}
|
||||
|
||||
bool QuestAvailabilityExpression::AndNode::operator==(const Node& other) const {
|
||||
bool QuestAvailabilityExpression::UnaryOperatorNode::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;
|
||||
const UnaryOperatorNode& other_un = dynamic_cast<const UnaryOperatorNode&>(other);
|
||||
return other_un.type == this->type && *other_un.sub == *this->sub;
|
||||
} catch (const bad_cast&) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool QuestAvailabilityExpression::AndNode::evaluate(
|
||||
const QuestFlagsForDifficulty& flags, shared_ptr<const TeamIndex::Team> team) const {
|
||||
return this->left->evaluate(flags, team) && this->right->evaluate(flags, team);
|
||||
}
|
||||
|
||||
string QuestAvailabilityExpression::AndNode::str() const {
|
||||
return "(" + this->left->str() + ") && (" + this->right->str() + ")";
|
||||
}
|
||||
|
||||
QuestAvailabilityExpression::NotNode::NotNode(unique_ptr<const Node>&& sub)
|
||||
: sub(std::move(sub)) {}
|
||||
|
||||
bool QuestAvailabilityExpression::NotNode::operator==(const Node& other) const {
|
||||
try {
|
||||
const NotNode& other_not = dynamic_cast<const NotNode&>(other);
|
||||
return *other_not.sub == *this->sub;
|
||||
} catch (const bad_cast&) {
|
||||
return false;
|
||||
int64_t QuestAvailabilityExpression::UnaryOperatorNode::evaluate(const Env& env) const {
|
||||
switch (this->type) {
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
bool QuestAvailabilityExpression::NotNode::evaluate(
|
||||
const QuestFlagsForDifficulty& flags, shared_ptr<const TeamIndex::Team> team) const {
|
||||
return !this->sub->evaluate(flags, team);
|
||||
}
|
||||
|
||||
string QuestAvailabilityExpression::NotNode::str() const {
|
||||
return "!(" + this->sub->str() + ")";
|
||||
string QuestAvailabilityExpression::UnaryOperatorNode::str() const {
|
||||
switch (this->type) {
|
||||
case Type::LOGICAL_NOT:
|
||||
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)
|
||||
@@ -101,9 +176,8 @@ bool QuestAvailabilityExpression::FlagLookupNode::operator==(const Node& other)
|
||||
}
|
||||
}
|
||||
|
||||
bool QuestAvailabilityExpression::FlagLookupNode::evaluate(
|
||||
const QuestFlagsForDifficulty& flags, shared_ptr<const TeamIndex::Team>) const {
|
||||
return flags.get(this->flag_index);
|
||||
int64_t QuestAvailabilityExpression::FlagLookupNode::evaluate(const Env& env) const {
|
||||
return env.flags->get(this->flag_index) ? 1 : 0;
|
||||
}
|
||||
|
||||
string QuestAvailabilityExpression::FlagLookupNode::str() const {
|
||||
@@ -122,15 +196,28 @@ bool QuestAvailabilityExpression::TeamRewardLookupNode::operator==(const Node& o
|
||||
}
|
||||
}
|
||||
|
||||
bool QuestAvailabilityExpression::TeamRewardLookupNode::evaluate(
|
||||
const QuestFlagsForDifficulty&, shared_ptr<const TeamIndex::Team> team) const {
|
||||
return team && team->has_reward(this->reward_name);
|
||||
int64_t QuestAvailabilityExpression::TeamRewardLookupNode::evaluate(const Env& env) const {
|
||||
return (env.team && env.team->has_reward(this->reward_name)) ? 1 : 0;
|
||||
}
|
||||
|
||||
string QuestAvailabilityExpression::TeamRewardLookupNode::str() const {
|
||||
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)
|
||||
: value(value) {}
|
||||
|
||||
@@ -143,13 +230,12 @@ bool QuestAvailabilityExpression::ConstantNode::operator==(const Node& other) co
|
||||
}
|
||||
}
|
||||
|
||||
bool QuestAvailabilityExpression::ConstantNode::evaluate(
|
||||
const QuestFlagsForDifficulty&, shared_ptr<const TeamIndex::Team>) const {
|
||||
int64_t QuestAvailabilityExpression::ConstantNode::evaluate(const Env&) const {
|
||||
return this->value;
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -186,48 +272,66 @@ unique_ptr<const QuestAvailabilityExpression::Node> QuestAvailabilityExpression:
|
||||
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
|
||||
size_t paren_level = 0;
|
||||
size_t and_pos = 0;
|
||||
size_t or_pos = 0;
|
||||
for (size_t z = 0; z < text.size() - 1; z++) {
|
||||
if (text[z] == '(') {
|
||||
paren_level++;
|
||||
} else if (text[z] == ')') {
|
||||
paren_level--;
|
||||
} else if ((text[z] == '&') && (text[z + 1] == '&') && !paren_level) {
|
||||
and_pos = z;
|
||||
} else if ((text[z] == '|') && (text[z + 1] == '|') && !paren_level) {
|
||||
or_pos = z;
|
||||
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;
|
||||
for (size_t z = 0; z < text.size() - 1; z++) {
|
||||
if (text[z] == '(') {
|
||||
paren_level++;
|
||||
continue;
|
||||
} else if (text[z] == ')') {
|
||||
paren_level--;
|
||||
continue;
|
||||
}
|
||||
if (!paren_level) {
|
||||
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
|
||||
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
|
||||
// Check for env lookups
|
||||
if (text.starts_with("F_")) {
|
||||
char* endptr = nullptr;
|
||||
uint64_t flag = strtoul(text.data() + 2, &endptr, 16);
|
||||
@@ -239,10 +343,27 @@ unique_ptr<const QuestAvailabilityExpression::Node> QuestAvailabilityExpression:
|
||||
}
|
||||
return make_unique<FlagLookupNode>(flag);
|
||||
}
|
||||
|
||||
if (text.starts_with("T_")) {
|
||||
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");
|
||||
}
|
||||
|
||||
@@ -15,6 +15,12 @@
|
||||
|
||||
class QuestAvailabilityExpression {
|
||||
public:
|
||||
struct Env {
|
||||
const QuestFlagsForDifficulty* flags;
|
||||
std::shared_ptr<const TeamIndex::Team> team;
|
||||
size_t num_players;
|
||||
};
|
||||
|
||||
QuestAvailabilityExpression(const std::string& text);
|
||||
~QuestAvailabilityExpression() = default;
|
||||
inline bool operator==(const QuestAvailabilityExpression& other) const {
|
||||
@@ -23,8 +29,8 @@ public:
|
||||
inline bool operator!=(const QuestAvailabilityExpression& other) const {
|
||||
return !this->operator==(other);
|
||||
}
|
||||
inline bool evaluate(const QuestFlagsForDifficulty& flags, std::shared_ptr<const TeamIndex::Team> team) const {
|
||||
return this->root->evaluate(flags, team);
|
||||
inline int64_t evaluate(const Env& env) const {
|
||||
return this->root->evaluate(env);
|
||||
}
|
||||
inline std::string str() const {
|
||||
return this->root->str();
|
||||
@@ -38,48 +44,62 @@ protected:
|
||||
inline bool operator!=(const Node& other) const {
|
||||
return !this->operator==(other);
|
||||
}
|
||||
virtual bool evaluate(const QuestFlagsForDifficulty& flags, std::shared_ptr<const TeamIndex::Team> team) const = 0;
|
||||
virtual int64_t evaluate(const Env& env) const = 0;
|
||||
virtual std::string str() const = 0;
|
||||
|
||||
protected:
|
||||
Node() = default;
|
||||
};
|
||||
|
||||
class OrNode : public Node {
|
||||
class BinaryOperatorNode : public Node {
|
||||
public:
|
||||
OrNode(std::unique_ptr<const Node>&& left, std::unique_ptr<const Node>&& right);
|
||||
virtual ~OrNode() = default;
|
||||
enum class Type {
|
||||
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 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;
|
||||
|
||||
protected:
|
||||
Type type;
|
||||
std::unique_ptr<const Node> left;
|
||||
std::unique_ptr<const Node> right;
|
||||
};
|
||||
|
||||
class AndNode : public Node {
|
||||
class UnaryOperatorNode : public Node {
|
||||
public:
|
||||
AndNode(std::unique_ptr<const Node>&& left, std::unique_ptr<const Node>&& right);
|
||||
virtual ~AndNode() = default;
|
||||
enum class Type {
|
||||
LOGICAL_NOT = 0,
|
||||
BITWISE_NOT,
|
||||
NEGATIVE,
|
||||
};
|
||||
UnaryOperatorNode(Type type, std::unique_ptr<const Node>&& sub);
|
||||
virtual ~UnaryOperatorNode() = default;
|
||||
virtual bool operator==(const Node& other) const;
|
||||
virtual bool evaluate(const QuestFlagsForDifficulty& flags, std::shared_ptr<const TeamIndex::Team> team) const;
|
||||
virtual std::string str() const;
|
||||
|
||||
protected:
|
||||
std::unique_ptr<const Node> left;
|
||||
std::unique_ptr<const Node> right;
|
||||
};
|
||||
|
||||
class NotNode : public Node {
|
||||
public:
|
||||
NotNode(std::unique_ptr<const Node>&& sub);
|
||||
virtual ~NotNode() = default;
|
||||
virtual bool operator==(const Node& other) const;
|
||||
virtual bool evaluate(const QuestFlagsForDifficulty& flags, std::shared_ptr<const TeamIndex::Team> team) const;
|
||||
virtual int64_t evaluate(const Env& env) const;
|
||||
virtual std::string str() const;
|
||||
|
||||
protected:
|
||||
Type type;
|
||||
std::unique_ptr<const Node> sub;
|
||||
};
|
||||
|
||||
@@ -88,7 +108,7 @@ protected:
|
||||
FlagLookupNode(uint16_t flag_index);
|
||||
virtual ~FlagLookupNode() = default;
|
||||
virtual bool operator==(const Node& other) const;
|
||||
virtual bool evaluate(const QuestFlagsForDifficulty& flags, std::shared_ptr<const TeamIndex::Team> team) const;
|
||||
virtual int64_t evaluate(const Env& env) const;
|
||||
virtual std::string str() const;
|
||||
|
||||
protected:
|
||||
@@ -100,23 +120,32 @@ protected:
|
||||
TeamRewardLookupNode(const std::string& reward_name);
|
||||
virtual ~TeamRewardLookupNode() = default;
|
||||
virtual bool operator==(const Node& other) const;
|
||||
virtual bool evaluate(const QuestFlagsForDifficulty& flags, std::shared_ptr<const TeamIndex::Team> team) const;
|
||||
virtual int64_t evaluate(const Env& env) const;
|
||||
virtual std::string str() const;
|
||||
|
||||
protected:
|
||||
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 {
|
||||
public:
|
||||
ConstantNode(bool value);
|
||||
virtual ~ConstantNode() = default;
|
||||
virtual bool operator==(const Node& other) const;
|
||||
virtual bool evaluate(const QuestFlagsForDifficulty& flags, std::shared_ptr<const TeamIndex::Team> team) const;
|
||||
virtual int64_t evaluate(const Env& env) const;
|
||||
virtual std::string str() const;
|
||||
|
||||
protected:
|
||||
bool value;
|
||||
int64_t value;
|
||||
};
|
||||
|
||||
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.");
|
||||
break;
|
||||
}
|
||||
if (game->quest && !c->can_play_quest(game->quest, game->difficulty)) {
|
||||
send_lobby_message_box(c, "$C6You don't have access\nto the quest in progress\nin this game.");
|
||||
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, or there\nis no space for another\nplayer in the quest.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,14 +37,15 @@
|
||||
// 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
|
||||
// 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
|
||||
// example with random values is shown below. This field is ignored if the
|
||||
// player has the DISABLE_QUEST_REQUIREMENTS flag in their license.
|
||||
// "AvailableIf": "(F_016D || F_0171 || T_EpicCustomQuest) && !F_0173",
|
||||
// be an integral expression that tests one or more flags or team rewards, or
|
||||
// the number of players. An example with random values is shown below. This
|
||||
// field is ignored if the player has the DISABLE_QUEST_REQUIREMENTS flag in
|
||||
// their license.
|
||||
// "AvailableIf": "(F_016D || T_EpicCustomQuest || (V_NumPlayers <= 2)) && !F_0173",
|
||||
|
||||
// On BB, quests may be disabled but still visible to the player. This
|
||||
// expression controls when that should be the case. If AvailableIf evaluates
|
||||
// to false, this is ignored. This field is also ignored if the player has
|
||||
// to false, this is ignored. This field is also ignored if the player has
|
||||
// the DISABLE_QUEST_REQUIREMENTS flag in their license.
|
||||
// "EnabledIf": "!F_0169",
|
||||
}
|
||||
|
||||
@@ -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