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