diff --git a/README-EN.md b/README-EN.md index 8f3653da..669a71c4 100644 --- a/README-EN.md +++ b/README-EN.md @@ -228,7 +228,7 @@ and the `params` field of matched variant will be merged into the parameters of **Note**: If the `filename` field is a relative path, it will be relative to `$MAA_CONFIG_DIR/infrast`. Besides, the custom infrastructure plan file will not be read by `maa-cli` but `MaaCore`. So the format of the file must be `JSON` and time period defined in the file will not be used to select the corresponding sub-plan. So you must specify the `plan_index` field in the parameters of the task to use the correct infrastructure plan in the corresponding time period. This will ensure that the correct infrastructure plan is used in the appropriate time period. -Besides of `Time` condition, there are also `DateTime`, `Weakday`, and `Combined` conditions. `DateTime` condition is used to specify a specific datetime period, `Weekday` condition is used to specify some days in a week, `Combined` condition is used to specify a combination of multiple conditions. +Besides of `Time` condition, there are also `DateTime`, `Weakday`, conditions. `DateTime` condition is used to specify a specific datetime period, `Weekday` condition is used to specify some days in a week. ```toml [[tasks]] @@ -249,6 +249,8 @@ params = { stage = "1-7" } Beside of above conditions, there is a condition `OnSideStory` which depends on hot update resource to check if there is any opening side story. Thus, the condition of fight `SL-8`can be simplified as `{ type = "OnSideStory", client = "Official" }`, where `client` is the client type of game. +Beside of above basic condition, `{ type = "And", conditions = [...] }` `{ type = "Or", conditions = [...] }`, and `{ type = "Not", condition = ... }` can be used for logical combination of conditions. + With default strategy, if multiple variants are matched, only the first one will be used. And if the condition is not given, the variant will always be matched. So you can put a variant without condition at the end of variants. The strategy of matching variants can be changed by `strategy` field: @@ -262,7 +264,7 @@ strategy = "merge" # or "first" (default) [[tasks.variants]] params = { expiring_medicine = 1000 } [tasks.variants.condition] -type = "Combined" +type = "And" conditions = [ { type = "Time", start = "18:00:00" }, { type = "Weekday", weekdays = ["Sun"] }, diff --git a/README.md b/README.md index ac0b0539..18f9e035 100644 --- a/README.md +++ b/README.md @@ -241,7 +241,7 @@ params = { plan_index = 0 } **注意**:如果你的自定义基建计划文件使用相对路径,应该相对于 `$MAA_CONFIG_DIR/infrast`。此外,由于基建文件是由 `MaaCore` 而不是 `maa-cli` 读取的,因此这些文件的格式必须是 `JSON`。同时,`maa-cli` 不会读取基建文件,也不会根据其中定义的时间段来选择相应的子计划。因此,必须通过 `condition` 字段来指定在相应时间段使用正确的基建计划的参数中的 `plan_index` 字段。这样可以确保在适当的时间段使用正确的基建计划。 -除了 `Time` 条件,还有 `DateTime`,`Weakday` 以及 `Combined` 条件。`DateTime` 条件用于指定一个时间段,`Weekday` 条件用于指定一周中的某些天,`Combined` 条件用于指定多个条件的组合。 +除了 `Time` 条件,还有 `DateTime`,`Weakday` 条件。`DateTime` 条件用于指定一个时间段,`Weekday` 条件用于指定一周中的某些天。 ```toml [[tasks]] @@ -264,6 +264,8 @@ params = { stage = "1-7" } 除了上述确定的条件之外,还有一个依赖于热更新资源的条件 `OnSideStory`,当你启动该条件后,`maa-cli` 会尝试读取相应的资源来判断当前是否有正在开启的活动,如果有那么对应的变体会被匹配。 比如上述夏活期间刷 `SL-8` 的条件就可以简化为 `{ type = "OnSideStory", client = "Official" }`,这里的 `client` 参数用于确定你使用的客户端,因为不同的客户端的活动时间不同,对于使用官服或者 b 服的用户,这可以省略。通过这个条件,每次活动更新之后你可以只需要更新需要刷的关卡而不需要手动编辑对应活动的开放时间。 +除了以上基础条件之外,你可以使用 `{ type = "And", conditions = [...] }`,`{ type = "Or", conditions = [...] }`, `{ type = "Not", condition = ... }` 来对条件进行逻辑运算。 + 在默认的策略下,如果有多个变体被匹配,第一个将会被使用。如果没有给出条件,那么变体将会总是被匹配,所以你可以把没有条件的变体放在最后,作为默认的情况。 你可以使用 `strategy` 字段来改变匹配策略: @@ -278,7 +280,7 @@ strategy = "merge" # 或者 "first" (默认) params = { expiring_medicine = 1000 } [tasks.variants.condition] -type = "Combined" +type = "And" conditions = [ { type = "Time", start = "18:00:00" }, { type = "Weekday", weekdays = ["Sun"] }, diff --git a/maa-cli/schemas/task.schema.json b/maa-cli/schemas/task.schema.json index b0d95a31..b6781f83 100644 --- a/maa-cli/schemas/task.schema.json +++ b/maa-cli/schemas/task.schema.json @@ -165,7 +165,7 @@ { "type": "object", "properties": { - "type": { "const": "Combined" }, + "type": { "const": "And" }, "conditions": { "type": "array", "items": { "$ref": "#/definitions/condition" } @@ -173,6 +173,27 @@ }, "required": ["type", "conditions"], "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { "const": "Or" }, + "conditions": { + "type": "array", + "items": { "$ref": "#/definitions/condition" } + } + }, + "required": ["type", "conditions"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "type": { "const": "Not" }, + "condition": { "$ref": "#/definitions/condition" } + }, + "required": ["type", "condition"], + "additionalProperties": false } ] }, diff --git a/maa-cli/src/config/task/condition.rs b/maa-cli/src/config/task/condition.rs index 031c33c6..cd8e7a01 100644 --- a/maa-cli/src/config/task/condition.rs +++ b/maa-cli/src/config/task/condition.rs @@ -13,9 +13,6 @@ pub enum Condition { /// The task is always active #[default] Always, - /// The task is never active, only used for testing - #[cfg(test)] - Never, /// The task is active on the specified weekdays Weekday { weekdays: Vec }, /// The task is active on the specified time range @@ -43,7 +40,12 @@ pub enum Condition { client: ClientType, }, /// The task is active if all the sub-conditions are met - Combined { conditions: Vec }, + #[serde(alias = "Combined")] + And { conditions: Vec }, + /// The task is active if any of the sub-conditions is met + Or { conditions: Vec }, + /// The task is active if the inner condition is not met + Not { condition: Box }, } fn deserialize_from_str<'de, S, D>(deserializer: D) -> Result, D::Error> @@ -66,8 +68,6 @@ impl Condition { pub fn is_active(&self) -> bool { match self { Condition::Always => true, - #[cfg(test)] - Condition::Never => false, Condition::Weekday { weekdays } => { let now = Local::now(); let weekday = now.date_naive().weekday(); @@ -93,7 +93,7 @@ impl Condition { } } Condition::OnSideStory { client } => has_side_story_open(*client), - Condition::Combined { conditions } => { + Condition::And { conditions } => { for condition in conditions { if !condition.is_active() { return false; @@ -101,6 +101,15 @@ impl Condition { } true } + Condition::Or { conditions } => { + for condition in conditions { + if condition.is_active() { + return true; + } + } + false + } + Condition::Not { condition } => !condition.is_active(), } } } @@ -230,35 +239,47 @@ mod tests { // fn on_side_story() {} #[test] - fn combined() { - let now = chrono::Local::now(); - let now_time = now.time(); - let weekday = now.date_naive().weekday(); - - assert!(Condition::Combined { + fn boolean() { + assert!(Condition::And { + conditions: vec![Condition::Always, Condition::Always] + } + .is_active()); + assert!(!Condition::And { conditions: vec![ - Condition::Time { - start: Some(now_time + Duration::minutes(-10)), - end: Some(now_time + Duration::minutes(10)), - }, - Condition::Weekday { - weekdays: vec![weekday] + Condition::Always, + Condition::Not { + condition: Box::new(Condition::Always) }, ] } .is_active()); - assert!(!Condition::Combined { + + assert!(Condition::Or { conditions: vec![ - Condition::Time { - start: Some(now_time + Duration::minutes(10)), - end: Some(now_time + Duration::minutes(20)), - }, - Condition::Weekday { - weekdays: vec![weekday] + Condition::Always, + Condition::Not { + condition: Box::new(Condition::Always) + } + ] + } + .is_active()); + + assert!(!Condition::Or { + conditions: vec![ + Condition::Not { + condition: Box::new(Condition::Always) }, + Condition::Not { + condition: Box::new(Condition::Always) + } ] } .is_active()); + + assert!(!Condition::Not { + condition: Box::new(Condition::Always) + } + .is_active()); } } @@ -375,5 +396,94 @@ mod tests { ], ); } + + #[test] + fn boolean() { + assert_de_tokens( + &Condition::And { + conditions: vec![Condition::Always, Condition::Always], + }, + &[ + Token::Map { len: Some(2) }, + Token::Str("type"), + Token::Str("Combined"), + Token::Str("conditions"), + Token::Seq { len: Some(2) }, + Token::Map { len: Some(1) }, + Token::Str("type"), + Token::Str("Always"), + Token::MapEnd, + Token::Map { len: Some(1) }, + Token::Str("type"), + Token::Str("Always"), + Token::MapEnd, + Token::SeqEnd, + Token::MapEnd, + ], + ); + + assert_de_tokens( + &Condition::And { + conditions: vec![Condition::Always, Condition::Always], + }, + &[ + Token::Map { len: Some(2) }, + Token::Str("type"), + Token::Str("And"), + Token::Str("conditions"), + Token::Seq { len: Some(2) }, + Token::Map { len: Some(1) }, + Token::Str("type"), + Token::Str("Always"), + Token::MapEnd, + Token::Map { len: Some(1) }, + Token::Str("type"), + Token::Str("Always"), + Token::MapEnd, + Token::SeqEnd, + Token::MapEnd, + ], + ); + + assert_de_tokens( + &Condition::Or { + conditions: vec![Condition::Always, Condition::Always], + }, + &[ + Token::Map { len: Some(2) }, + Token::Str("type"), + Token::Str("Or"), + Token::Str("conditions"), + Token::Seq { len: Some(2) }, + Token::Map { len: Some(1) }, + Token::Str("type"), + Token::Str("Always"), + Token::MapEnd, + Token::Map { len: Some(1) }, + Token::Str("type"), + Token::Str("Always"), + Token::MapEnd, + Token::SeqEnd, + Token::MapEnd, + ], + ); + + assert_de_tokens( + &Condition::Not { + condition: Box::new(Condition::Always), + }, + &[ + Token::Map { len: Some(2) }, + Token::Str("type"), + Token::Str("Not"), + Token::Str("condition"), + Token::Map { len: Some(1) }, + Token::Str("type"), + Token::Str("Always"), + Token::MapEnd, + Token::MapEnd, + ], + ); + } } } diff --git a/maa-cli/src/config/task/mod.rs b/maa-cli/src/config/task/mod.rs index bbf23a2e..207e697c 100644 --- a/maa-cli/src/config/task/mod.rs +++ b/maa-cli/src/config/task/mod.rs @@ -363,7 +363,9 @@ mod tests { fn never_active() -> TaskVariant { TaskVariant { - condition: Condition::Never, + condition: Condition::Not { + condition: Box::new(Condition::Always), + }, params: MAAValue::default(), } } @@ -470,7 +472,9 @@ mod tests { Strategy::First, vec![ TaskVariant { - condition: Condition::Never, + condition: Condition::Not { + condition: Box::new(Condition::Always), + }, params: object!("a" => 2), }, TaskVariant {