wowlab_parsers/parsers/transform/loot/
journal.rs1use wowlab_types::{
2 data::{EncounterEntry, InstanceKind, JournalInstanceFlat, JournalTierFlat, TierInstanceEntry},
3 sim::IntMap,
4};
5
6use super::shared::{
7 encounter_image_path, instance_kind, is_mythic_plus_map, journal_image_path,
8 loadscreen_image_path, loadscreen_seo_path, primary_encounter_creature,
9 resolve_loadscreen_file_data_id,
10};
11use crate::parsers::dbc::DbcData;
12
13const CURRENT_JOURNAL_TIER_ID: i32 = 505;
14
15pub fn transform_all_journal_tiers(dbc: &DbcData) -> Vec<JournalTierFlat> {
17 let Some(tier) = dbc.journal_tier.get(&CURRENT_JOURNAL_TIER_ID) else {
18 tracing::warn!(
19 tier_id = CURRENT_JOURNAL_TIER_ID,
20 "JournalTier row for current season not found; skipping journal tier emission"
21 );
22
23 return Vec::new();
24 };
25
26 let mut links: Vec<&crate::parsers::dbc::rows::JournalTierXInstanceRow> = dbc
27 .journal_tier_x_instance
28 .values()
29 .flatten()
30 .filter(|link| link.JournalTierID == tier.ID)
31 .collect();
32
33 links.sort_by_key(|link| link.OrderIndex);
34
35 let instances: Vec<TierInstanceEntry> = links
36 .into_iter()
37 .filter_map(|link| {
38 let instance = dbc.journal_instance.get(&link.JournalInstanceID)?;
39 let map_id = instance.MapID;
40 let kind = instance_kind(dbc, map_id, link.OrderIndex, instance.Flags);
41
42 Some(TierInstanceEntry {
43 instance_id: instance.ID,
44 instance_name: instance.Name_lang.clone().unwrap_or_default(),
45 kind,
46 order_index: link.OrderIndex,
47 map_id,
48 is_mythic_plus: is_mythic_plus_map(dbc, map_id),
49 })
50 })
51 .collect();
52
53 vec![JournalTierFlat {
54 id: tier.ID,
55 name: tier.Name_lang.clone().unwrap_or_default(),
56 expansion: tier.Expansion,
57 player_condition_id: tier.PlayerConditionID,
58 instances,
59 }]
60}
61
62#[must_use]
64pub fn transform_all_journal_instances(dbc: &DbcData) -> Vec<JournalInstanceFlat> {
65 let mut encounters_by_instance: IntMap<
66 i32,
67 Vec<&crate::parsers::dbc::rows::JournalEncounterRow>,
68 > = IntMap::default();
69
70 for encounter in dbc.journal_encounter.values() {
71 encounters_by_instance
72 .entry(encounter.JournalInstanceID)
73 .or_default()
74 .push(encounter);
75 }
76
77 for list in encounters_by_instance.values_mut() {
78 list.sort_by_key(|e| e.OrderIndex);
79 }
80
81 let mut links_by_instance: IntMap<
82 i32,
83 Vec<&crate::parsers::dbc::rows::JournalTierXInstanceRow>,
84 > = IntMap::default();
85
86 for links in dbc.journal_tier_x_instance.values() {
87 for link in links {
88 links_by_instance
89 .entry(link.JournalInstanceID)
90 .or_default()
91 .push(link);
92 }
93 }
94
95 let mut instances: Vec<&crate::parsers::dbc::rows::JournalInstanceRow> = dbc
96 .journal_tier_x_instance
97 .keys()
98 .filter_map(|instance_id| dbc.journal_instance.get(instance_id))
99 .collect();
100
101 instances.sort_by_key(|instance| instance.ID);
102
103 instances
104 .into_iter()
105 .filter_map(|instance| {
106 let links = links_by_instance.get(&instance.ID)?;
107 let primary_link = primary_journal_tier_link(dbc, links)?;
108 let tier = dbc.journal_tier.get(&primary_link.JournalTierID)?;
109 let map_id = instance.MapID;
110 let kind = instance_kind(dbc, map_id, primary_link.OrderIndex, instance.Flags);
111
112 if !matches!(kind, InstanceKind::Raid | InstanceKind::Dungeon) {
113 return None;
114 }
115
116 let instance_name = instance.Name_lang.clone().unwrap_or_default();
117 let loadscreen_file_data_id = resolve_loadscreen_file_data_id(dbc, &instance_name);
118 let encounters: Vec<EncounterEntry> = encounters_by_instance
119 .get(&instance.ID)
120 .map(|list| {
121 list.iter()
122 .map(|e| EncounterEntry {
123 creature_display_info_id: primary_encounter_creature(dbc, e.ID)
124 .map(|creature| creature.CreatureDisplayInfoID)
125 .unwrap_or_default(),
126 id: e.ID,
127 image_file_data_id: primary_encounter_creature(dbc, e.ID)
128 .map(|creature| creature.FileDataID)
129 .unwrap_or_default(),
130 image_storage_path: encounter_image_path(
131 primary_encounter_creature(dbc, e.ID)
132 .map(|creature| creature.FileDataID)
133 .unwrap_or_default(),
134 ),
135 name: e.Name_lang.clone().unwrap_or_default(),
136 order_index: e.OrderIndex,
137 dungeon_encounter_id: e.DungeonEncounterID,
138 ui_map_id: e.UiMapID,
139 ui_model_scene_id: primary_encounter_creature(dbc, e.ID)
140 .map(|creature| creature.UiModelSceneID)
141 .unwrap_or_default(),
142 })
143 .collect()
144 })
145 .unwrap_or_default();
146
147 Some(JournalInstanceFlat {
148 background_file_data_id: instance.BackgroundFileDataID,
149 background_storage_path: journal_image_path(instance.BackgroundFileDataID),
150 button_file_data_id: instance.ButtonFileDataID,
151 button_small_file_data_id: instance.ButtonSmallFileDataID,
152 button_storage_path: journal_image_path(instance.ButtonFileDataID),
153 id: instance.ID,
154 name: instance_name,
155 description: instance.Description_lang.clone().unwrap_or_default(),
156 lore_file_data_id: instance.LoreFileDataID,
157 lore_storage_path: journal_image_path(instance.LoreFileDataID),
158 loadscreen_file_data_id,
159 loadscreen_seo_storage_path: loadscreen_seo_path(loadscreen_file_data_id),
160 loadscreen_storage_path: loadscreen_image_path(loadscreen_file_data_id),
161 map_id,
162 kind,
163 is_mythic_plus: is_mythic_plus_map(dbc, map_id),
164 is_current_season: links
165 .iter()
166 .any(|link| link.JournalTierID == CURRENT_JOURNAL_TIER_ID),
167 tier_id: tier.ID,
168 tier_name: tier.Name_lang.clone().unwrap_or_default(),
169 tier_order_index: primary_link.OrderIndex,
170 encounters,
171 })
172 })
173 .collect()
174}
175
176fn primary_journal_tier_link<'a>(
177 dbc: &DbcData,
178 links: &[&'a crate::parsers::dbc::rows::JournalTierXInstanceRow],
179) -> Option<&'a crate::parsers::dbc::rows::JournalTierXInstanceRow> {
180 links
181 .iter()
182 .copied()
183 .find(|link| link.JournalTierID == CURRENT_JOURNAL_TIER_ID)
184 .or_else(|| {
185 links.iter().copied().max_by_key(|link| {
186 dbc.journal_tier
187 .get(&link.JournalTierID)
188 .map(|tier| (tier.Expansion, tier.ID))
189 .unwrap_or_default()
190 })
191 })
192}