From 70c57e772711f06bbe097a3d802b0cac82e89df9 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Thu, 7 Mar 2024 21:18:51 -0800 Subject: [PATCH] add V_V1Present token in quest conditions --- src/Client.cc | 43 +++++++++++++----------------- src/Client.hh | 10 +++++-- src/Lobby.cc | 23 +++++++++++----- src/Lobby.hh | 1 + src/QuestAvailabilityExpression.cc | 17 ++++++++++++ src/QuestAvailabilityExpression.hh | 10 +++++++ system/quests/battle/b88001.json | 16 +++++++---- 7 files changed, 83 insertions(+), 37 deletions(-) diff --git a/src/Client.cc b/src/Client.cc index 2c97d1ab..205912f7 100644 --- a/src/Client.cc +++ b/src/Client.cc @@ -349,46 +349,41 @@ shared_ptr Client::team() const { return team; } -bool Client::can_see_quest(shared_ptr q, uint8_t event, uint8_t difficulty, size_t num_players) const { +bool Client::evaluate_quest_availability_expression( + shared_ptr expr, + uint8_t event, + uint8_t difficulty, + size_t num_players, + bool v1_present) const { if (this->license && this->license->check_flag(License::Flag::DISABLE_QUEST_REQUIREMENTS)) { return true; } - if (!q->available_expression) { + if (!expr) { return true; } auto p = this->character(); - string expr = q->available_expression->str(); QuestAvailabilityExpression::Env env = { .flags = &p->quest_flags.data.at(difficulty), .challenge_records = &p->challenge_records, .team = this->team(), .num_players = num_players, .event = event, + .v1_present = v1_present, }; - int64_t ret = q->available_expression->evaluate(env); - this->log.info("Evaluated quest availability expression %s => %s", expr.c_str(), ret ? "TRUE" : "FALSE"); + int64_t ret = expr->evaluate(env); + if (this->log.should_log(LogLevel::INFO)) { + string expr_str = expr->str(); + this->log.info("Evaluated quest availability expression %s => %s", expr_str.c_str(), ret ? "TRUE" : "FALSE"); + } return ret; } -bool Client::can_play_quest(shared_ptr q, uint8_t event, uint8_t difficulty, size_t num_players) const { - if (this->license && this->license->check_flag(License::Flag::DISABLE_QUEST_REQUIREMENTS)) { - return true; - } - if (!q->enabled_expression) { - return true; - } - auto p = this->character(); - string expr = q->enabled_expression->str(); - QuestAvailabilityExpression::Env env = { - .flags = &p->quest_flags.data.at(difficulty), - .challenge_records = &p->challenge_records, - .team = this->team(), - .num_players = num_players, - .event = event, - }; - bool ret = q->enabled_expression->evaluate(env); - this->log.info("Evaluating quest enabled expression %s => %s", expr.c_str(), ret ? "TRUE" : "FALSE"); - return ret; +bool Client::can_see_quest(shared_ptr q, uint8_t event, uint8_t difficulty, size_t num_players, bool v1_present) const { + return this->evaluate_quest_availability_expression(q->available_expression, event, difficulty, num_players, v1_present); +} + +bool Client::can_play_quest(shared_ptr q, uint8_t event, uint8_t difficulty, size_t num_players, bool v1_present) const { + return this->evaluate_quest_availability_expression(q->enabled_expression, event, difficulty, num_players, v1_present); } bool Client::can_use_chat_commands() const { diff --git a/src/Client.hh b/src/Client.hh index aba9db09..ff5142e2 100644 --- a/src/Client.hh +++ b/src/Client.hh @@ -290,8 +290,14 @@ public: std::shared_ptr team() const; - bool can_see_quest(std::shared_ptr q, uint8_t event, uint8_t difficulty, size_t num_players) const; - bool can_play_quest(std::shared_ptr q, uint8_t event, uint8_t difficulty, size_t num_players) const; + bool evaluate_quest_availability_expression( + std::shared_ptr expr, + uint8_t event, + uint8_t difficulty, + size_t num_players, + bool v1_present) const; + bool can_see_quest(std::shared_ptr q, uint8_t event, uint8_t difficulty, size_t num_players, bool v1_present) const; + bool can_play_quest(std::shared_ptr q, uint8_t event, uint8_t difficulty, size_t num_players, bool v1_present) const; bool can_use_chat_commands() const; diff --git a/src/Lobby.cc b/src/Lobby.cc index c6f179b8..519c2794 100644 --- a/src/Lobby.cc +++ b/src/Lobby.cc @@ -496,13 +496,22 @@ bool Lobby::any_client_loading() const { size_t Lobby::count_clients() const { size_t ret = 0; for (size_t x = 0; x < this->max_clients; x++) { - if (this->clients[x].get()) { + if (this->clients[x]) { ret++; } } return ret; } +bool Lobby::any_v1_clients_present() const { + for (size_t x = 0; x < this->max_clients; x++) { + if (this->clients[x] && is_v1(this->clients[x]->version())) { + return true; + } + } + return false; +} + void Lobby::add_client(shared_ptr c, ssize_t required_client_id) { ssize_t index; ssize_t min_client_id = this->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM) ? 4 : 0; @@ -752,8 +761,9 @@ Lobby::JoinError Lobby::join_error_for_client(std::shared_ptr c, const s } if (this->quest) { size_t num_clients = this->count_clients() + 1; - if (!c->can_see_quest(this->quest, this->event, this->difficulty, num_clients) || - !c->can_play_quest(this->quest, this->event, this->difficulty, num_clients)) { + bool v1_present = is_v1(c->version()) || this->any_v1_clients_present(); + if (!c->can_see_quest(this->quest, this->event, this->difficulty, num_clients, v1_present) || + !c->can_play_quest(this->quest, this->event, this->difficulty, num_clients, v1_present)) { return JoinError::NO_ACCESS_TO_QUEST; } } @@ -862,13 +872,14 @@ unordered_map> Lobby::clients_by_serial_number() co QuestIndex::IncludeCondition Lobby::quest_include_condition() const { size_t num_players = this->count_clients(); - return [this, num_players](shared_ptr q) -> QuestIndex::IncludeState { + bool v1_present = this->any_v1_clients_present(); + return [this, num_players, v1_present](shared_ptr q) -> QuestIndex::IncludeState { bool is_enabled = true; for (const auto& lc : this->clients) { - if (lc && !lc->can_see_quest(q, this->event, this->difficulty, num_players)) { + if (lc && !lc->can_see_quest(q, this->event, this->difficulty, num_players, v1_present)) { return QuestIndex::IncludeState::HIDDEN; } - if (lc && !lc->can_play_quest(q, this->event, this->difficulty, num_players)) { + if (lc && !lc->can_play_quest(q, this->event, this->difficulty, num_players, v1_present)) { is_enabled = false; } } diff --git a/src/Lobby.hh b/src/Lobby.hh index 60e3f497..a3ae20b8 100644 --- a/src/Lobby.hh +++ b/src/Lobby.hh @@ -252,6 +252,7 @@ struct Lobby : public std::enable_shared_from_this { void reassign_leader_on_client_departure(size_t leaving_client_id); size_t count_clients() const; + bool any_v1_clients_present() const; bool any_client_loading() const; void add_client(std::shared_ptr c, ssize_t required_client_id = -1); diff --git a/src/QuestAvailabilityExpression.cc b/src/QuestAvailabilityExpression.cc index 53ddc025..f2a72e2a 100644 --- a/src/QuestAvailabilityExpression.cc +++ b/src/QuestAvailabilityExpression.cc @@ -259,6 +259,20 @@ string QuestAvailabilityExpression::EventLookupNode::str() const { return "V_Event"; } +QuestAvailabilityExpression::V1PresenceLookupNode::V1PresenceLookupNode() {} + +bool QuestAvailabilityExpression::V1PresenceLookupNode::operator==(const Node& other) const { + return dynamic_cast(&other) != nullptr; +} + +int64_t QuestAvailabilityExpression::V1PresenceLookupNode::evaluate(const Env& env) const { + return env.v1_present ? 1 : 0; +} + +string QuestAvailabilityExpression::V1PresenceLookupNode::str() const { + return "V_V1Present"; +} + QuestAvailabilityExpression::ConstantNode::ConstantNode(int64_t value) : value(value) {} @@ -412,6 +426,9 @@ unique_ptr QuestAvailabilityExpression: if (text == "V_Event") { return make_unique(); } + if (text == "V_V1Present") { + return make_unique(); + } // Check for constants if (text == "true") { diff --git a/src/QuestAvailabilityExpression.hh b/src/QuestAvailabilityExpression.hh index bf0f3c7b..37946d95 100644 --- a/src/QuestAvailabilityExpression.hh +++ b/src/QuestAvailabilityExpression.hh @@ -21,6 +21,7 @@ public: std::shared_ptr team; size_t num_players; uint8_t event; + bool v1_present; }; QuestAvailabilityExpression(const std::string& text); @@ -160,6 +161,15 @@ protected: virtual std::string str() const; }; + class V1PresenceLookupNode : public Node { + public: + V1PresenceLookupNode(); + virtual ~V1PresenceLookupNode() = 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(int64_t value); diff --git a/system/quests/battle/b88001.json b/system/quests/battle/b88001.json index bc35cd74..8f7dc917 100644 --- a/system/quests/battle/b88001.json +++ b/system/quests/battle/b88001.json @@ -36,11 +36,17 @@ // 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 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. + // value for AvailableIf in the quest's JSON file. (This is ignored if the + // player has the DISABLE_QUEST_REQUIREMENTS flag in their license.) This + // field's value should be an expression that tests any of the following: + // F_XXXX: Quest flag specified in hex (e.g. F_014D) + // CC_EpX_Y: Whether or not Challenge stage X in Episode Y is complete + // T_ZZZ: Whether or not the player's BB team has reward ZZZ + // V_NumPlayers: The number of players in the current game + // V_Event: The holiday event in the current game + // V_V1Present: Whether there are any V1 players in the current game + // You can also use constants, parentheses, and many common integer and + // boolean operators. An example expression with random values is shown here. // "AvailableIf": "(F_016D || T_EpicCustomQuest || (V_NumPlayers <= 2)) && !F_0173", // On BB, quests may be disabled but still visible to the player. This