Skip to content

Commit c1bfd13

Browse files
committed
feat: Add achievement icons
1 parent 79995e8 commit c1bfd13

File tree

4 files changed

+62
-12
lines changed

4 files changed

+62
-12
lines changed

src/task.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,7 @@ pub fn load_wows_files(wows_directory: PathBuf, locale: &str) -> Result<Backgrou
490490
ship_icons: icons,
491491
ribbon_icons,
492492
subribbon_icons,
493+
achievement_icons: HashMap::new(),
493494
replays_dir: replays_dir.clone(),
494495
build_dir,
495496
};

src/ui/replay_parser/mod.rs

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -723,18 +723,18 @@ impl UiReport {
723723
return None;
724724
};
725725

726-
let achievement_name = metadata_provider
727-
.localized_name_from_id(&format!("IDS_ACHIEVEMENT_{}", achievement_data.ui_name()))?;
726+
let ui_name = achievement_data.ui_name().to_string();
727+
let achievement_name =
728+
metadata_provider.localized_name_from_id(&format!("IDS_ACHIEVEMENT_{ui_name}"))?;
728729

729-
let achievement_description = metadata_provider.localized_name_from_id(&format!(
730-
"IDS_ACHIEVEMENT_DESCRIPTION_{}",
731-
achievement_data.ui_name()
732-
))?;
730+
let achievement_description = metadata_provider
731+
.localized_name_from_id(&format!("IDS_ACHIEVEMENT_DESCRIPTION_{ui_name}"))?;
733732

734733
Some(Achievement {
735734
game_param,
736735
display_name: achievement_name,
737736
description: achievement_description,
737+
icon_key: ui_name,
738738
count: achievement_count as usize,
739739
})
740740
})
@@ -1620,14 +1620,27 @@ impl UiReport {
16201620
if !report.achievements.is_empty() {
16211621
ui.strong("Achievements");
16221622

1623+
let mut wows_data = self.wows_data.write();
16231624
for achievement in &report.achievements {
1624-
let response = if achievement.count > 1 {
1625-
ui.label(format!("{} ({}x)", &achievement.display_name, achievement.count))
1626-
} else {
1627-
ui.label(&achievement.display_name)
1628-
};
1629-
response.on_hover_text(&achievement.description);
1625+
ui.horizontal(|ui| {
1626+
if let Some(icon) = wows_data.achievement_icon(&achievement.icon_key) {
1627+
let image = Image::new(ImageSource::Bytes {
1628+
uri: icon.path.clone().into(),
1629+
bytes: icon.data.clone().into(),
1630+
})
1631+
.fit_to_exact_size((32.0, 32.0).into());
1632+
ui.add(image).on_hover_text(&achievement.description);
1633+
}
1634+
1635+
let response = if achievement.count > 1 {
1636+
ui.label(format!("{} ({}x)", &achievement.display_name, achievement.count))
1637+
} else {
1638+
ui.label(&achievement.display_name)
1639+
};
1640+
response.on_hover_text(&achievement.description);
1641+
});
16301642
}
1643+
drop(wows_data);
16311644
}
16321645

16331646
// Display ribbons
@@ -3492,8 +3505,18 @@ impl ToolkitTabViewer<'_> {
34923505
if !all_achievements.is_empty() {
34933506
ui.strong("Achievements");
34943507

3508+
let mut wows_data = self.tab_state.world_of_warships_data.as_ref().unwrap().write();
34953509
for achievement in all_achievements {
34963510
ui.horizontal(|ui| {
3511+
if let Some(icon) = wows_data.achievement_icon(&achievement.icon_key) {
3512+
let image = Image::new(ImageSource::Bytes {
3513+
uri: icon.path.clone().into(),
3514+
bytes: icon.data.clone().into(),
3515+
})
3516+
.fit_to_exact_size((32.0, 32.0).into());
3517+
ui.add(image).on_hover_text(&achievement.description);
3518+
}
3519+
34973520
let response = ui.label(achievement.display_name);
34983521
response.on_hover_text(&achievement.description);
34993522

@@ -3502,6 +3525,7 @@ impl ToolkitTabViewer<'_> {
35023525
});
35033526
});
35043527
}
3528+
drop(wows_data);
35053529
}
35063530

35073531
ui.separator();

src/ui/replay_parser/models.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ pub struct Achievement {
209209
pub game_param: Arc<Param>,
210210
pub display_name: String,
211211
pub description: String,
212+
pub icon_key: String,
212213
pub count: usize,
213214
}
214215

src/wows_data.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ pub struct WorldOfWarshipsData {
4949
/// Subribbon icons keyed by ribbon name (e.g., "ribbon_main_caliber")
5050
pub subribbon_icons: HashMap<String, Arc<GameAsset>>,
5151

52+
/// Achievement icons, lazy-loaded and cached. Keyed by achievement name (lowercase).
53+
pub achievement_icons: HashMap<String, Arc<GameAsset>>,
54+
5255
#[allow(dead_code)]
5356
pub full_version: Option<wowsunpack::data::Version>,
5457
pub patch_version: usize,
@@ -59,6 +62,27 @@ pub struct WorldOfWarshipsData {
5962
pub build_dir: PathBuf,
6063
}
6164

65+
impl WorldOfWarshipsData {
66+
/// Get an achievement icon by name, lazy-loading and caching it from the game files.
67+
/// The icon_key should be the lowercase achievement name (e.g., "pve_honorsstar").
68+
pub fn achievement_icon(&mut self, icon_key: &str) -> Option<Arc<GameAsset>> {
69+
if let Some(icon) = self.achievement_icons.get(icon_key) {
70+
return Some(icon.clone());
71+
}
72+
73+
let path = format!("gui/achievements/icon_achievement_{icon_key}.png");
74+
let icon_node = self.file_tree.find(&path).ok()?;
75+
let file_info = icon_node.file_info()?;
76+
77+
let mut icon_data = Vec::with_capacity(file_info.unpacked_size as usize);
78+
icon_node.read_file(&self.pkg_loader, &mut icon_data).ok()?;
79+
80+
let asset = Arc::new(GameAsset { path, data: icon_data });
81+
self.achievement_icons.insert(icon_key.to_string(), asset.clone());
82+
Some(asset)
83+
}
84+
}
85+
6286
/// Shared dependencies needed for loading and parsing replays.
6387
/// This bundles together all the Arc-wrapped state that replay loading requires.
6488
#[derive(Clone)]

0 commit comments

Comments
 (0)