1use crate::block::Block;
2#[cfg(feature = "vic3")]
3use crate::context::ScopeContext;
4use crate::datacontext::DataContext;
5use crate::datatype::Datatype;
6use crate::db::{Db, DbKind};
7use crate::everything::Everything;
8use crate::game::{Game, GameFlags};
9use crate::gui::validate_datatype_field;
10use crate::item::{Item, ItemLoader};
11use crate::scopes::Scopes;
12use crate::token::Token;
13use crate::tooltipped::Tooltipped;
14use crate::validator::{Validator, ValueValidator};
15
16#[derive(Clone, Debug)]
17pub struct TutorialLesson {}
18
19inventory::submit! {
20 ItemLoader::Normal(GameFlags::Ck3.union(GameFlags::Vic3), Item::TutorialLesson, TutorialLesson::add)
21}
22
23impl TutorialLesson {
24 pub fn add(db: &mut Db, key: Token, block: Block) {
25 db.add(Item::TutorialLesson, key, block, Box::new(Self {}));
26 }
27}
28
29impl DbKind for TutorialLesson {
30 fn add_subitems(&self, _key: &Token, block: &Block, db: &mut Db) {
31 let chain = block.get_field_value("chain");
32 for (key, block) in block.iter_definitions() {
33 if !key.is("trigger") && !key.is("trigger_transition") {
34 db.add(
35 Item::TutorialLessonStep,
36 key.clone(),
37 block.clone(),
38 Box::new(TutorialLessonStep { chain: chain.cloned() }),
39 );
40 }
41 }
42 }
43
44 #[allow(unused_variables)] fn validate(&self, key: &Token, block: &Block, data: &Everything) {
46 let mut vd = Validator::new(block, data);
47
48 vd.field_item("chain", Item::TutorialLessonChain);
49 vd.field_bool("start_automatically");
50
51 vd.field_trigger_rooted("trigger", Tooltipped::No, game_tutorial_scope());
52
53 vd.multi_field_value("gui_tag");
57 vd.field_value("highlight_widget");
59 vd.field_validated_key("highlight_widget_dynamic_loc", |key, bv, data| {
61 validate_datatype_field(
62 Datatype::Unknown,
63 key,
64 bv,
65 data,
66 &mut DataContext::new(),
67 false,
68 );
69 });
70 #[cfg(feature = "vic3")]
71 {
72 let mut sc = ScopeContext::new(Scopes::JournalEntry, key);
73 vd.multi_field_target("highlight_target", &mut sc, Scopes::all());
74 }
75
76 vd.multi_field_validated_block("trigger_transition", validate_trigger_transition);
77
78 vd.field_integer("delay");
79 vd.field_integer("default_lesson_step_delay");
80
81 vd.field_bool("finish_gamestate_tutorial");
82 vd.field_bool("shown_in_encyclopedia");
83 vd.field_item("encyclopedia_text", Item::Localization);
84
85 vd.unknown_block_fields(|_, _| ());
87 }
88}
89
90#[derive(Clone, Debug)]
91pub struct TutorialLessonChain {
92 gamestate_tutorial: bool,
93}
94
95inventory::submit! {
96 ItemLoader::Normal(GameFlags::Ck3.union(GameFlags::Vic3), Item::TutorialLessonChain, TutorialLessonChain::add)
97}
98
99impl TutorialLessonChain {
100 pub fn add(db: &mut Db, key: Token, block: Block) {
101 let gamestate_tutorial =
102 block.get_field_bool("save_progress_in_gamestate").unwrap_or(false);
103 db.add(Item::TutorialLessonChain, key, block, Box::new(Self { gamestate_tutorial }));
104 }
105}
106
107impl DbKind for TutorialLessonChain {
108 fn validate(&self, _key: &Token, block: &Block, data: &Everything) {
109 let mut vd = Validator::new(block, data);
110
111 vd.field_trigger_rooted("trigger", Tooltipped::No, game_tutorial_scope());
113 vd.field_integer("delay");
114 vd.field_bool("save_progress_in_gamestate");
115 }
116
117 fn has_property(
118 &self,
119 _key: &Token,
120 _block: &Block,
121 property: &str,
122 _data: &Everything,
123 ) -> bool {
124 property == "gamestate_tutorial" && self.gamestate_tutorial
125 }
126
127 fn merge_in(&mut self, other: Box<dyn DbKind>) {
128 if let Some(other) = other.as_any().downcast_ref::<Self>() {
129 self.gamestate_tutorial |= other.gamestate_tutorial;
130 }
131 }
132}
133
134#[derive(Clone, Debug)]
136pub struct TutorialLessonStep {
137 chain: Option<Token>,
138}
139
140impl DbKind for TutorialLessonStep {
141 fn validate(&self, key: &Token, block: &Block, data: &Everything) {
142 let mut vd = Validator::new(block, data);
143
144 data.verify_exists(Item::Localization, key);
145
146 vd.field_item("text", Item::Localization);
147 vd.field_item("header_info", Item::Localization); vd.multi_field_value("gui_tag");
151 vd.field_value("window_name");
152 vd.multi_field_value("highlight_widget");
154 vd.field_validated_key("highlight_widget_dynamic_loc", |key, bv, data| {
156 validate_datatype_field(
157 Datatype::Unknown,
158 key,
159 bv,
160 data,
161 &mut DataContext::new(),
162 false,
163 );
164 });
165 #[cfg(feature = "vic3")]
166 {
167 let mut sc = ScopeContext::new(Scopes::JournalEntry, key);
168 vd.multi_field_target("highlight_target", &mut sc, Scopes::all());
169 }
170
171 vd.field_item("soundeffect", Item::Sound);
173 vd.field_item("voice", Item::Sound);
174
175 vd.field_bool("repeat_sound_effect");
176 vd.field_integer("delay");
177 vd.field_value("animation");
178 vd.field_bool("shown_in_encyclopedia");
179 vd.field_item("encyclopedia_text", Item::Localization);
180
181 vd.multi_field_validated_block("gui_transition", |block, data| {
182 let mut vd = Validator::new(block, data);
183 vd.field_value("button_id");
184 vd.field_item("button_text", Item::Localization);
185 vd.field_validated_value("target", validate_lesson_target);
186 vd.field_trigger_rooted("enabled", Tooltipped::No, game_tutorial_scope());
187 });
188 vd.multi_field_validated_block("trigger_transition", validate_trigger_transition);
189
190 vd.field_effect_rooted("interface_effect", Tooltipped::No, Scopes::None);
193
194 if self.chain.as_ref().is_some_and(|t| {
195 data.item_has_property(Item::TutorialLessonChain, t.as_str(), "gamestate_tutorial")
196 }) {
197 vd.field_bool("pause_game");
198 vd.field_bool("force_pause_game");
199 vd.field_effect_rooted("effect", Tooltipped::No, game_tutorial_scope());
200 } else {
201 vd.ban_field("pause_game", || "gamestate tutorial chains");
202 vd.ban_field("force_pause_game", || "gamestate tutorial chains");
203 vd.ban_field("effect", || "gamestate tutorial chains");
204 }
205
206 #[cfg(feature = "ck3")]
207 vd.field_validated_block("highlight_widget_with_index", |block, data| {
208 let mut vd = Validator::new(block, data);
209 vd.unknown_value_fields(|_, value| {
210 let mut vvd = ValueValidator::new(value, data);
212 vvd.integer();
213 });
214 });
215 #[cfg(feature = "ck3")]
216 vd.field_validated_block("highlight_child_widget_of", |block, data| {
217 let mut vd = Validator::new(block, data);
218 vd.unknown_value_fields(|_, _| {
219 });
221 });
222 }
223}
224
225fn validate_trigger_transition(block: &Block, data: &Everything) {
226 let mut vd = Validator::new(block, data);
227
228 vd.field_trigger_rooted("trigger", Tooltipped::No, game_tutorial_scope());
229 vd.field_validated_value("target", validate_lesson_target);
230 vd.field_value("button_id");
231 vd.field_item("button_text", Item::Localization);
232}
233
234fn validate_lesson_target(_key: &Token, mut vd: ValueValidator) {
235 vd.maybe_is("lesson_finish");
236 vd.maybe_is("lesson_abort");
237 vd.item(Item::TutorialLessonStep);
238}
239
240fn game_tutorial_scope() -> Scopes {
241 match Game::game() {
242 #[cfg(feature = "ck3")]
243 Game::Ck3 => Scopes::Character,
244 #[cfg(feature = "vic3")]
245 Game::Vic3 => Scopes::Country,
246 #[cfg(feature = "imperator")]
247 Game::Imperator => unimplemented!(),
248 #[cfg(feature = "hoi4")]
249 Game::Hoi4 => unimplemented!(),
250 }
251}