1use crate::{bundles::BundleElement, prelude::*, screen_ref::ScreenType};
2use strum_macros::{Display, EnumString};
3
4use crate::{bundles::BundleId, currency::CurrencyUnit};
5
6#[declare]
7pub type QuestId = Uuid;
8
9#[derive(
10 Clone,
11 Copy,
12 Debug,
13 Serialize,
14 Deserialize,
15 JsonSchema,
16 EnumString,
17 Display,
18 PartialEq,
19 Eq,
20 Tsify,
21 Hash,
22)]
23pub enum QuestGroupType {
24 Daily,
25 Weekly,
26 Lifetime,
27 LoopTask,
28 PatronDaily,
29 PatronLifetime,
30 Hidden,
31 Achievement,
32 ProgressPass,
33}
34
35#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash, JsonSchema, Tsify)]
36pub struct QuestTemplate {
37 #[schemars(schema_with = "id_schema")]
38 pub id: QuestId,
39
40 #[schemars(title = "Название квеста")]
41 pub title: i18n::I18nString,
42 #[schemars(title = "Описание квеста")]
43 pub description: i18n::I18nString,
44
45 #[schemars(
46 title = "Нативная функция прогресса",
47 description = "Имя нативной функции категории conditional_progress, вычисляющей прогресс квеста.",
48 schema_with = "conditional_progress_ref_schema"
49 )]
50 #[serde(default)]
51 pub progress_behavior: Option<String>,
52
53 #[schemars(title = "Конечная цель по квесту")]
54 pub progress_target: i64,
55
56 #[schemars(title = "Тип группы квестов")]
57 pub quest_group_type: QuestGroupType,
58
59 #[schemars(
60 title = "Бандл награды за квест",
61 description = "ID бандла с наградами за выполнение квеста",
62 schema_with = "option_bundle_id_schema"
63 )]
64 pub bundle_id: Option<BundleId>,
65
66 #[schemars(title = "Очки прогресса для группы за выполнения квеста")]
67 pub progression_points: u64,
68
69 #[schemars(title = "Выдается ли квест на создании персонажа")]
70 pub starting: bool,
71
72 #[schemars(
73 title = "Квесты, которые выдаются по завершению данного квеста",
74 schema_with = "quest_link_id_array_schema"
75 )]
76 pub next_quest_ids: Vec<QuestId>,
77
78 #[schemars(title = "Подписка на эвенты")]
79 pub events_subscribe: Vec<String>,
80
81 #[schemars(
82 title = "Нативная функция доп. квестов",
83 description = "Имя нативной функции категории additional_quests, вычисляющей дополнительные квесты.",
84 schema_with = "additional_quests_ref_schema"
85 )]
86 #[serde(default)]
87 pub additional_quests_behavior: Option<String>,
88
89 #[schemars(title = "Засчитывается ли прогресс по повторяющемуся квесту, если он не активен")]
90 pub progress_if_inactive: bool,
91
92 #[schemars(title = "На какой экран ссылается квест")]
93 pub screen_reference: Option<ScreenType>,
94
95 #[schemars(title = "Код квеста для цепочки loop task")]
96 pub code: Option<String>,
97}
98
99#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Tsify)]
100pub struct QuestInstance {
101 pub id: QuestId,
102 pub current: i64,
103 pub reward: Vec<BundleElement>,
104 pub is_claimed: bool,
105}
106
107impl QuestInstance {
108 pub fn is_completed(&self, target: i64) -> bool {
109 self.current >= target
110 }
111}
112
113#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Tsify)]
114pub struct QuestsProgressionPointSettings {
115 #[schemars(title = "Очки прогресса по квесту")]
116 pub points: u64,
117
118 #[schemars(
119 title = "Награда (валюты)",
120 description = "Фиксированная валютная награда за достижение очков прогресса."
121 )]
122 #[serde(default)]
123 pub reward: Vec<CurrencyUnit>,
124}
125
126#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Tsify)]
127pub struct QuestsProgressionSettings {
128 #[schemars(title = "Настройки ежедневного квеста")]
129 pub daily: Vec<QuestsProgressionPointSettings>,
130
131 #[schemars(title = "Настройки еженедельного квеста")]
132 pub weekly: Vec<QuestsProgressionPointSettings>,
133
134 #[schemars(title = "Настройки ачивок")]
135 pub achievement: Vec<QuestsProgressionPointSettings>,
136}
137
138#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Tsify)]
139pub struct QuestsProgressTrack {
140 pub current_points: u64,
141 pub period_end_date: chrono::DateTime<chrono::Utc>,
142 pub rewards: Vec<QuestsTrackReward>,
143}
144
145#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Tsify)]
146pub struct QuestsTrackReward {
147 pub points_required: u64,
148 pub reward: Vec<CurrencyUnit>,
149 pub is_claimed: bool,
150}
151
152#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Tsify)]
153pub struct QuestsProgressionGroup {
154 pub progress_track: QuestsProgressTrack,
155 pub quests: Vec<QuestInstance>,
156}
157
158#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Tsify)]
159pub struct QuestsGroups {
160 pub daily: QuestsProgressionGroup,
161 pub weekly: QuestsProgressionGroup,
162 pub lifetime: Vec<QuestInstance>,
163 pub loop_tasks: Vec<QuestInstance>,
164 pub patron_daily: Vec<QuestInstance>,
165 pub patron_lifetime: Vec<QuestInstance>,
166 pub hidden: Vec<QuestInstance>,
167 pub achievements: QuestsProgressionGroup,
168 pub progress_pass: Vec<QuestInstance>,
169}
170
171impl QuestsGroups {
172 pub fn iter(&self) -> impl Iterator<Item = &QuestInstance> {
173 self.lifetime
174 .iter()
175 .chain(self.loop_tasks.iter())
176 .chain(self.patron_daily.iter())
177 .chain(self.patron_lifetime.iter())
178 .chain(self.daily.quests.iter())
179 .chain(self.weekly.quests.iter())
180 .chain(self.hidden.iter())
181 .chain(self.achievements.quests.iter())
182 .chain(self.progress_pass.iter())
183 }
184
185 pub fn iter_repeatable(&self) -> impl Iterator<Item = &QuestInstance> {
186 self.daily.quests.iter().chain(self.weekly.quests.iter())
187 }
188
189 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut QuestInstance> {
190 self.daily
191 .quests
192 .iter_mut()
193 .chain(self.weekly.quests.iter_mut())
194 .chain(self.lifetime.iter_mut())
195 .chain(self.loop_tasks.iter_mut())
196 .chain(self.patron_daily.iter_mut())
197 .chain(self.patron_lifetime.iter_mut())
198 .chain(self.hidden.iter_mut())
199 .chain(self.achievements.quests.iter_mut())
200 .chain(self.progress_pass.iter_mut())
201 }
202
203 pub fn mark_quest_claimed(&mut self, quest_id: QuestId) -> anyhow::Result<()> {
204 if let Some(quest) = self
205 .daily
206 .quests
207 .iter_mut()
208 .find(|q| q.id == quest_id && !q.is_claimed)
209 {
210 quest.is_claimed = true;
211 return Ok(());
212 }
213
214 if let Some(quest) = self
215 .weekly
216 .quests
217 .iter_mut()
218 .find(|q| q.id == quest_id && !q.is_claimed)
219 {
220 quest.is_claimed = true;
221 return Ok(());
222 }
223
224 if let Some(quest) = self
225 .lifetime
226 .iter_mut()
227 .find(|q| q.id == quest_id && !q.is_claimed)
228 {
229 quest.is_claimed = true;
230 return Ok(());
231 }
232
233 if let Some(quest) = self
234 .loop_tasks
235 .iter_mut()
236 .find(|q| q.id == quest_id && !q.is_claimed)
237 {
238 quest.is_claimed = true;
239 return Ok(());
240 }
241
242 if let Some(quest) = self
243 .patron_daily
244 .iter_mut()
245 .find(|q| q.id == quest_id && !q.is_claimed)
246 {
247 quest.is_claimed = true;
248 return Ok(());
249 }
250
251 if let Some(quest) = self
252 .patron_lifetime
253 .iter_mut()
254 .find(|q| q.id == quest_id && !q.is_claimed)
255 {
256 quest.is_claimed = true;
257 return Ok(());
258 }
259
260 if let Some(quest) = self
261 .achievements
262 .quests
263 .iter_mut()
264 .find(|q| q.id == quest_id && !q.is_claimed)
265 {
266 quest.is_claimed = true;
267 return Ok(());
268 }
269
270 if let Some(quest) = self
271 .progress_pass
272 .iter_mut()
273 .find(|q| q.id == quest_id && !q.is_claimed)
274 {
275 quest.is_claimed = true;
276 return Ok(());
277 }
278
279 anyhow::bail!("Didn't find unclaimed quest with id: {quest_id}")
280 }
281
282 pub fn get_not_claimed_quests(&self) -> Vec<&QuestInstance> {
283 let mut quests = vec![];
284
285 quests.extend(self.daily.quests.iter().filter(|q| !q.is_claimed));
286
287 quests.extend(self.weekly.quests.iter().filter(|q| !q.is_claimed));
288
289 quests.extend(self.lifetime.iter().filter(|q| !q.is_claimed));
290
291 quests.extend(self.loop_tasks.iter().filter(|q| !q.is_claimed));
292
293 quests.extend(self.patron_daily.iter().filter(|q| !q.is_claimed));
294
295 quests.extend(self.patron_lifetime.iter().filter(|q| !q.is_claimed));
296
297 quests.extend(self.hidden.iter().filter(|q| !q.is_claimed));
298
299 quests.extend(self.achievements.quests.iter().filter(|q| !q.is_claimed));
300
301 quests.extend(self.progress_pass.iter().filter(|q| !q.is_claimed));
302
303 quests
304 }
305
306 pub fn get_not_claimed_quests_mut(&mut self) -> Vec<&mut QuestInstance> {
307 let mut quests = vec![];
308
309 quests.extend(self.daily.quests.iter_mut().filter(|q| !q.is_claimed));
310
311 quests.extend(self.weekly.quests.iter_mut().filter(|q| !q.is_claimed));
312
313 quests.extend(self.lifetime.iter_mut().filter(|q| !q.is_claimed));
314
315 quests.extend(self.loop_tasks.iter_mut().filter(|q| !q.is_claimed));
316
317 quests.extend(self.patron_daily.iter_mut().filter(|q| !q.is_claimed));
318
319 quests.extend(self.patron_lifetime.iter_mut().filter(|q| !q.is_claimed));
320
321 quests.extend(self.hidden.iter_mut().filter(|q| !q.is_claimed));
322
323 quests.extend(
324 self.achievements
325 .quests
326 .iter_mut()
327 .filter(|q| !q.is_claimed),
328 );
329
330 quests.extend(self.progress_pass.iter_mut().filter(|q| !q.is_claimed));
331
332 quests
333 }
334
335 pub fn len(&self) -> usize {
336 self.daily.quests.len()
337 + self.weekly.quests.len()
338 + self.lifetime.len()
339 + self.loop_tasks.len()
340 + self.patron_daily.len()
341 + self.patron_lifetime.len()
342 + self.hidden.len()
343 + self.achievements.quests.len()
344 + self.progress_pass.len()
345 }
346
347 pub fn is_empty(&self) -> bool {
348 self.len() == 0
349 }
350
351 pub fn push(&mut self, quest: &QuestInstance, group_type: &QuestGroupType) {
352 match group_type {
353 QuestGroupType::Daily => self.daily.quests.push(quest.clone()),
354 QuestGroupType::Weekly => self.weekly.quests.push(quest.clone()),
355 QuestGroupType::Lifetime => self.lifetime.push(quest.clone()),
356 QuestGroupType::LoopTask => self.loop_tasks.push(quest.clone()),
357 QuestGroupType::PatronDaily => self.patron_daily.push(quest.clone()),
358 QuestGroupType::PatronLifetime => self.patron_lifetime.push(quest.clone()),
359 QuestGroupType::Hidden => self.hidden.push(quest.clone()),
360 QuestGroupType::Achievement => self.achievements.quests.push(quest.clone()),
361 QuestGroupType::ProgressPass => self.progress_pass.push(quest.clone()),
362 }
363 }
364
365 pub fn retain_all(&mut self, quest_id: QuestId) {
366 self.daily.quests.retain(|x| x.id != quest_id);
367 self.weekly.quests.retain(|x| x.id != quest_id);
368 self.lifetime.retain(|x| x.id != quest_id);
369 self.loop_tasks.retain(|x| x.id != quest_id);
370 self.patron_daily.retain(|x| x.id != quest_id);
371 self.patron_lifetime.retain(|x| x.id != quest_id);
372 self.hidden.retain(|x| x.id != quest_id);
373 self.achievements.quests.retain(|x| x.id != quest_id);
374 self.progress_pass.retain(|x| x.id != quest_id);
375 }
376
377 pub fn find_in_all(&self, quest_id: QuestId) -> Option<QuestInstance> {
378 self.daily
379 .quests
380 .iter()
381 .find(|x| x.id == quest_id)
382 .or_else(|| self.weekly.quests.iter().find(|x| x.id == quest_id))
383 .or_else(|| self.lifetime.iter().find(|x| x.id == quest_id))
384 .or_else(|| self.loop_tasks.iter().find(|x| x.id == quest_id))
385 .or_else(|| self.patron_daily.iter().find(|x| x.id == quest_id))
386 .or_else(|| self.patron_lifetime.iter().find(|x| x.id == quest_id))
387 .or_else(|| self.hidden.iter().find(|x| x.id == quest_id))
388 .or_else(|| self.achievements.quests.iter().find(|x| x.id == quest_id))
389 .or_else(|| self.progress_pass.iter().find(|x| x.id == quest_id))
390 .cloned()
391 }
392
393 pub fn retain_patron(&mut self, quest_id: QuestId) {
394 self.patron_daily.retain(|x| x.id != quest_id);
395 self.patron_lifetime.retain(|x| x.id != quest_id);
396 }
397
398 pub fn find_in_patron(&self, quest_id: QuestId) -> Option<QuestInstance> {
399 self.patron_daily
400 .iter()
401 .find(|x| x.id == quest_id)
402 .or_else(|| self.patron_lifetime.iter().find(|x| x.id == quest_id))
403 .cloned()
404 }
405
406 pub fn retain_repeatable(&mut self, quest_id: QuestId) {
407 self.daily.quests.retain(|x| x.id != quest_id);
408 self.weekly.quests.retain(|x| x.id != quest_id);
409 }
410
411 pub fn find_in_repeatable(&self, quest_id: QuestId) -> Option<QuestInstance> {
412 self.daily
413 .quests
414 .iter()
415 .find(|x| x.id == quest_id)
416 .or_else(|| self.weekly.quests.iter().find(|x| x.id == quest_id))
417 .cloned()
418 }
419
420 pub fn find_in_repeatable_mut(&mut self, quest_id: QuestId) -> Option<&mut QuestInstance> {
421 self.daily
422 .quests
423 .iter_mut()
424 .find(|x| x.id == quest_id)
425 .or_else(|| self.weekly.quests.iter_mut().find(|x| x.id == quest_id))
426 }
427
428 pub fn reset_loop_task(&mut self, quest_id: QuestId) {
431 self.loop_tasks.retain(|x| x.id != quest_id);
432 }
433
434 pub fn find_in_loop_tasks(&self, quest_id: QuestId) -> Option<QuestInstance> {
435 self.loop_tasks.iter().find(|x| x.id == quest_id).cloned()
436 }
437
438 pub fn retain_lifetime(&mut self, quest_id: QuestId) {
439 self.lifetime.retain(|x| x.id != quest_id);
440 }
441
442 pub fn find_in_lifetime(&self, quest_id: QuestId) -> Option<QuestInstance> {
443 self.lifetime.iter().find(|x| x.id == quest_id).cloned()
444 }
445
446 pub fn retain_hidden(&mut self, quest_id: QuestId) {
447 self.hidden.retain(|x| x.id != quest_id);
448 }
449
450 pub fn find_in_hidden(&self, quest_id: QuestId) -> Option<QuestInstance> {
451 self.hidden.iter().find(|x| x.id == quest_id).cloned()
452 }
453
454 pub fn find_in_non_patron(&self, quest_id: QuestId) -> Option<QuestInstance> {
455 self.find_in_lifetime(quest_id)
456 .or_else(|| self.find_in_loop_tasks(quest_id))
457 .or_else(|| self.find_in_repeatable(quest_id))
458 .or_else(|| self.find_in_achievements(quest_id))
459 }
460
461 pub fn retain_achievements(&mut self, quest_id: QuestId) {
462 self.achievements.quests.retain(|x| x.id != quest_id);
463 }
464
465 pub fn find_in_achievements(&self, quest_id: QuestId) -> Option<QuestInstance> {
466 self.achievements
467 .quests
468 .iter()
469 .find(|x| x.id == quest_id)
470 .cloned()
471 }
472}