Skip to content

Commit 27c5c81

Browse files
authored
Add job filter for recipes (#360)
This adds a new "Active Job Only" checkbox to the recipe pane which limits the shown recipes/missions to the currently selected job. Equivalent options are also added to the command line, the queries have been converted from nameless tuples to structs, and new tests are added.
1 parent 1563ada commit 27c5c81

10 files changed

Lines changed: 317 additions & 69 deletions

File tree

raphael-cli/src/commands/search_mission.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use clap::Args;
22
use raphael_data::{
3-
RECIPES, STELLAR_MISSIONS, StellarMission, get_job_name, get_stellar_mission_name,
3+
RECIPES, STELLAR_MISSIONS, StellarMission, StellarSearchQuery, get_job_id, get_job_name,
4+
get_stellar_mission_name,
45
};
56

67
use crate::commands::Language;
@@ -11,6 +12,10 @@ pub struct SearchArgs {
1112
#[arg(short, long, required_unless_present_any(["recipe_id", "item_id", "mission_id"]), conflicts_with_all(["recipe_id", "item_id"]))]
1213
pub pattern: Option<String>,
1314

15+
/// Job name to limit searches to
16+
#[arg(short, long, requires("pattern"), conflicts_with_all(["recipe_id", "item_id"]))]
17+
pub job: Option<String>,
18+
1419
/// Recipe ID to search for
1520
#[arg(short, long, required_unless_present_any(["pattern", "item_id", "mission_id"]), conflicts_with = "item_id")]
1621
pub recipe_id: Option<u32>,
@@ -44,7 +49,15 @@ pub fn execute(args: &SearchArgs) {
4449
);
4550
}
4651
if let Some(pattern_arg) = &args.pattern {
47-
matches.extend(raphael_data::find_stellar_missions(pattern_arg, locale));
52+
let job_id = args
53+
.job
54+
.as_ref()
55+
.map(|job_name| get_job_id(job_name, locale).expect("Unknown job!"));
56+
matches.extend(raphael_data::find_stellar_missions(StellarSearchQuery {
57+
text: pattern_arg,
58+
locale,
59+
job_id,
60+
}));
4861
}
4962
if let Some(recipe_id_arg) = args.recipe_id {
5063
matches.extend(

raphael-cli/src/commands/search_recipe.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
use clap::Args;
2-
use raphael_data::{RECIPES, Recipe, STELLAR_MISSIONS, get_job_name, get_raw_item_name};
2+
use raphael_data::{
3+
RECIPES, Recipe, RecipeSearchQuery, STELLAR_MISSIONS, get_job_id, get_job_name,
4+
get_raw_item_name,
5+
};
36

47
use crate::commands::Language;
58

@@ -9,6 +12,10 @@ pub struct SearchArgs {
912
#[arg(short, long, required_unless_present_any(["recipe_id", "item_id", "mission_id"]), conflicts_with_all(["recipe_id", "item_id"]))]
1013
pub pattern: Option<String>,
1114

15+
/// Job name to limit searches to
16+
#[arg(short, long, requires("pattern"), conflicts_with_all(["recipe_id", "item_id"]))]
17+
pub job: Option<String>,
18+
1219
/// Recipe ID to search for
1320
#[arg(short, long, required_unless_present_any(["pattern", "item_id", "mission_id"]), conflicts_with = "item_id")]
1421
pub recipe_id: Option<u32>,
@@ -55,7 +62,15 @@ pub fn execute(args: &SearchArgs) {
5562
}
5663
}
5764
if let Some(pattern_arg) = &args.pattern {
58-
matches.extend(raphael_data::find_recipes(pattern_arg, locale));
65+
let job_id = args
66+
.job
67+
.as_ref()
68+
.map(|job_name| get_job_id(job_name, locale).expect("Unknown job!"));
69+
matches.extend(raphael_data::find_recipes(RecipeSearchQuery {
70+
text: pattern_arg,
71+
locale,
72+
job_id,
73+
}));
5974
}
6075
if let Some(recipe_id_arg) = args.recipe_id {
6176
matches.extend(

raphael-data/benches/bench_game_data.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,25 @@ fn bench_random_access(c: &mut Criterion) {
2727

2828
fn bench_find_recipes(c: &mut Criterion) {
2929
c.bench_function("find_recipes", |b| {
30-
b.iter(|| find_recipes("", Locale::EN));
30+
b.iter(|| {
31+
find_recipes(RecipeSearchQuery {
32+
text: "",
33+
locale: Locale::EN,
34+
job_id: None,
35+
})
36+
});
3137
});
3238
}
3339

3440
fn bench_find_stellar_missions(c: &mut Criterion) {
3541
c.bench_function("find_stellar_missions", |b| {
36-
b.iter(|| find_stellar_missions("", Locale::EN));
42+
b.iter(|| {
43+
find_stellar_missions(StellarSearchQuery {
44+
text: "",
45+
locale: Locale::EN,
46+
job_id: None,
47+
})
48+
});
3749
});
3850
}
3951

raphael-data/src/locales.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,22 @@ pub fn get_job_name(job_id: u8, locale: Locale) -> &'static str {
6262
}
6363
}
6464

65+
pub fn get_job_id(job_name: &str, locale: Locale) -> Option<u8> {
66+
let job_names = match locale {
67+
Locale::EN => JOB_NAMES_EN,
68+
Locale::DE => JOB_NAMES_DE,
69+
Locale::FR => JOB_NAMES_FR,
70+
Locale::JP => JOB_NAMES_JP,
71+
Locale::CN => JOB_NAMES_CN,
72+
Locale::KR => JOB_NAMES_KR,
73+
Locale::TW => JOB_NAMES_TW,
74+
};
75+
job_names
76+
.iter()
77+
.position(|&name| name == job_name)
78+
.map(|i| i as u8)
79+
}
80+
6581
pub const ITEM_NAMES_EN: NciArray<u32, &str> = include!("../data/item_names_en.rs");
6682
pub const ITEM_NAMES_DE: NciArray<u32, &str> = include!("../data/item_names_de.rs");
6783
pub const ITEM_NAMES_FR: NciArray<u32, &str> = include!("../data/item_names_fr.rs");

raphael-data/src/search.rs

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ impl<T> AsRef<str> for MatcherCandidate<T> {
2929
}
3030
}
3131

32+
#[derive(Debug, Clone, Copy, PartialEq, Hash)]
33+
pub struct RecipeSearchQuery<'a> {
34+
pub text: &'a str,
35+
pub locale: crate::Locale,
36+
pub job_id: Option<u8>,
37+
}
38+
3239
fn preprocess_pattern(pattern: &str) -> String {
3340
pattern
3441
.chars()
@@ -37,17 +44,17 @@ fn preprocess_pattern(pattern: &str) -> String {
3744
}
3845

3946
pub type RecipeSearchEntry = (u32, &'static crate::Recipe);
40-
pub fn find_recipes(
41-
search_string: &str,
42-
locale: crate::Locale,
43-
) -> impl Iterator<Item = RecipeSearchEntry> {
47+
pub fn find_recipes(query: RecipeSearchQuery) -> impl Iterator<Item = RecipeSearchEntry> {
4448
let pattern = Pattern::parse(
45-
&preprocess_pattern(search_string),
49+
&preprocess_pattern(query.text),
4650
CaseMatching::Ignore,
4751
Normalization::Smart,
4852
);
4953
let entries = RECIPES.entries().filter_map(|(recipe_id, recipe)| {
50-
let item_name = get_raw_item_name(recipe.item_id, locale)?;
54+
let item_name = get_raw_item_name(recipe.item_id, query.locale)?;
55+
if query.job_id.is_some_and(|job_id| job_id != recipe.job_id) {
56+
return None;
57+
}
5158
Some(MatcherCandidate {
5259
haystack: item_name,
5360
associated_data: (recipe_id, recipe),
@@ -59,20 +66,28 @@ pub fn find_recipes(
5966
.map(|(entry, _score)| entry.associated_data)
6067
}
6168

69+
#[derive(Debug, Clone, Copy, PartialEq, Hash)]
70+
pub struct StellarSearchQuery<'a> {
71+
pub text: &'a str,
72+
pub locale: crate::Locale,
73+
pub job_id: Option<u8>,
74+
}
6275
pub type StellarMissionSearchEntry = (u32, &'static crate::StellarMission);
6376
pub fn find_stellar_missions(
64-
search_string: &str,
65-
locale: crate::Locale,
77+
query: StellarSearchQuery,
6678
) -> impl Iterator<Item = StellarMissionSearchEntry> {
6779
let pattern = Pattern::parse(
68-
&preprocess_pattern(search_string),
80+
&preprocess_pattern(query.text),
6981
CaseMatching::Ignore,
7082
Normalization::Smart,
7183
);
7284
let mission_entries = STELLAR_MISSIONS
7385
.entries()
7486
.filter_map(|(mission_id, mission)| {
75-
let mission_name = get_stellar_mission_name(mission_id, locale)?;
87+
let mission_name = get_stellar_mission_name(mission_id, query.locale)?;
88+
if query.job_id.is_some_and(|job_id| job_id != mission.job_id) {
89+
return None;
90+
}
7691
Some(MatcherCandidate {
7792
haystack: mission_name,
7893
associated_data: (mission_id, mission),
@@ -83,7 +98,10 @@ pub fn find_stellar_missions(
8398
.flat_map(|(mission_id, mission)| {
8499
mission.recipe_ids.iter().filter_map(move |&recipe_id| {
85100
let recipe = RECIPES.get(recipe_id)?;
86-
let item_name = get_raw_item_name(recipe.item_id, locale)?;
101+
let item_name = get_raw_item_name(recipe.item_id, query.locale)?;
102+
if query.job_id.is_some_and(|job_id| job_id != recipe.job_id) {
103+
return None;
104+
}
87105
Some(MatcherCandidate {
88106
haystack: item_name,
89107
associated_data: (mission_id, mission),

raphael-data/tests/test_recipes.rs

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,14 @@ fn all_recipe_items_exist() {
2727
fn find_recipes_exact(
2828
item_name: &str,
2929
locale: raphael_data::Locale,
30+
job_id: Option<u8>,
3031
) -> impl Iterator<Item = &'static raphael_data::Recipe> {
31-
raphael_data::find_recipes(item_name, locale).filter_map(move |(_recipe_id, recipe)| {
32+
raphael_data::find_recipes(RecipeSearchQuery {
33+
text: item_name,
34+
locale,
35+
job_id,
36+
})
37+
.filter_map(move |(_recipe_id, recipe)| {
3238
let recipe_item_name = raphael_data::get_raw_item_name(recipe.item_id, locale).unwrap();
3339
if item_name == recipe_item_name {
3440
Some(recipe)
@@ -40,7 +46,8 @@ fn find_recipes_exact(
4046

4147
#[test]
4248
fn medical_supplies() {
43-
let matching_recipes = find_recipes_exact("Medical Supplies", Locale::EN).collect::<Vec<_>>();
49+
let matching_recipes =
50+
find_recipes_exact("Medical Supplies", Locale::EN, None).collect::<Vec<_>>();
4451
let expected = expect![[r#"
4552
[
4653
Recipe {
@@ -362,7 +369,7 @@ fn medical_supplies() {
362369

363370
#[test]
364371
fn ipe_lumber() {
365-
let matching_recipes = find_recipes_exact("Ipe Lumber", Locale::EN).collect::<Vec<_>>();
372+
let matching_recipes = find_recipes_exact("Ipe Lumber", Locale::EN, None).collect::<Vec<_>>();
366373
let expected = expect![[r#"
367374
[
368375
Recipe {
@@ -412,7 +419,7 @@ fn ipe_lumber() {
412419
#[test]
413420
fn uncharted_course_resin() {
414421
let matching_recipes =
415-
find_recipes_exact("Uncharted Course Resin", Locale::EN).collect::<Vec<_>>();
422+
find_recipes_exact("Uncharted Course Resin", Locale::EN, None).collect::<Vec<_>>();
416423
let expected = expect![[r#"
417424
[
418425
Recipe {
@@ -500,7 +507,63 @@ fn uncharted_course_resin() {
500507

501508
#[test]
502509
fn habitat_chair() {
503-
let matching_recipes = find_recipes_exact("Habitat Chair", Locale::EN).collect::<Vec<_>>();
510+
let matching_recipes =
511+
find_recipes_exact("Habitat Chair", Locale::EN, None).collect::<Vec<_>>();
512+
let expected = expect![[r#"
513+
[
514+
Recipe {
515+
job_id: 0,
516+
item_id: 48295,
517+
max_level_scaling: 100,
518+
recipe_level: 690,
519+
progress_factor: 54,
520+
quality_factor: 87,
521+
durability_factor: 88,
522+
material_factor: 0,
523+
ingredients: [
524+
Ingredient {
525+
item_id: 0,
526+
amount: 0,
527+
},
528+
Ingredient {
529+
item_id: 0,
530+
amount: 0,
531+
},
532+
Ingredient {
533+
item_id: 0,
534+
amount: 0,
535+
},
536+
Ingredient {
537+
item_id: 0,
538+
amount: 0,
539+
},
540+
Ingredient {
541+
item_id: 0,
542+
amount: 0,
543+
},
544+
Ingredient {
545+
item_id: 0,
546+
amount: 0,
547+
},
548+
],
549+
is_expert: false,
550+
req_craftsmanship: 0,
551+
req_control: 0,
552+
},
553+
]
554+
"#]];
555+
expected.assert_debug_eq(&matching_recipes);
556+
}
557+
558+
#[test]
559+
fn habitat_chair_jobid() {
560+
let results_with_wrong_job =
561+
find_recipes_exact("Habitat Chair", Locale::EN, Some(5)).collect::<Vec<_>>();
562+
assert!(results_with_wrong_job.is_empty());
563+
564+
let matching_recipes =
565+
find_recipes_exact("Habitat Chair", Locale::EN, Some(0)).collect::<Vec<_>>();
566+
assert_eq!(matching_recipes.len(), 1);
504567
let expected = expect![[r#"
505568
[
506569
Recipe {

0 commit comments

Comments
 (0)