essences/
progress_pass.rs

1use crate::bundles::BundleId;
2use crate::offers::OfferTemplateId;
3use crate::prelude::*;
4use crate::quest::{QuestId, QuestInstance, QuestTemplate};
5use schema_loader::{bundle_id_schema, offer_link_id_schema, quest_link_id_array_schema};
6use strum::Display;
7use strum_macros::EnumString;
8
9#[derive(
10    Clone,
11    Copy,
12    Debug,
13    Display,
14    EnumString,
15    Serialize,
16    Deserialize,
17    PartialEq,
18    Eq,
19    JsonSchema,
20    Tsify,
21)]
22pub enum ProgressPassTrack {
23    #[strum(serialize = "free")]
24    Free,
25    #[strum(serialize = "paid")]
26    Paid,
27}
28
29#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Tsify)]
30pub struct ProgressPassTierTemplate {
31    #[schemars(title = "Номер тира (1, 2, 3...)")]
32    pub tier: u32,
33
34    #[schemars(
35        title = "ID квестов, все должны быть выполнены для разблокировки тира",
36        schema_with = "quest_link_id_array_schema"
37    )]
38    pub quest_template_ids: Vec<QuestId>,
39
40    #[schemars(
41        title = "ID бандла бесплатной награды",
42        schema_with = "bundle_id_schema"
43    )]
44    pub free_reward_bundle_id: BundleId,
45
46    #[schemars(title = "ID бандла платной награды", schema_with = "bundle_id_schema")]
47    pub paid_reward_bundle_id: BundleId,
48}
49
50#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Tsify)]
51pub struct ProgressPassConfig {
52    #[schemars(title = "Тиры прогресс пасса")]
53    pub tiers: Vec<ProgressPassTierTemplate>,
54
55    #[schemars(
56        title = "ID шаблона оффера для покупки премиума",
57        schema_with = "offer_link_id_schema"
58    )]
59    pub premium_offer_template_id: OfferTemplateId,
60}
61
62#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Tsify)]
63pub struct ProgressPassTierState {
64    pub tier: u32,
65    pub is_unlocked: bool,
66    pub free_claimed: bool,
67    pub paid_claimed: bool,
68}
69
70#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Tsify)]
71pub struct ProgressPassState {
72    pub has_premium: bool,
73    pub tiers: Vec<ProgressPassTierState>,
74}
75
76/// Authoritative check: are all quests that gate `tier_template` complete?
77///
78/// The `ProgressPassTierState.is_unlocked` field is a cached view of this over
79/// the wire; the server must not trust it for gating — it derives fresh from
80/// `quest_groups.progress_pass`.
81pub fn is_progress_pass_tier_unlocked(
82    tier_template: &ProgressPassTierTemplate,
83    progress_pass_quests: &[QuestInstance],
84    quest_templates: &[QuestTemplate],
85) -> bool {
86    tier_template.quest_template_ids.iter().all(|quest_id| {
87        let Some(qi) = progress_pass_quests.iter().find(|q| q.id == *quest_id) else {
88            return false;
89        };
90        let target = quest_templates
91            .iter()
92            .find(|q| q.id == *quest_id)
93            .map(|q| q.progress_target)
94            .unwrap_or(1);
95        qi.is_completed(target)
96    })
97}