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(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    /// Remove a claimed loop task so it can be re-created fresh
429    /// via NewQuests at the end of the list.
430    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}