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 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}