essences/
quest.rs

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