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