1use crate::gatings::AutoChestGatings;
2use crate::prelude::*;
3
4use crate::items::Attribute;
5use crate::items::AttributeId;
6use crate::items::Item;
7use crate::items::ItemRarity;
8use crate::items::ItemRarityId;
9
10#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Tsify)]
11pub struct AutoChest {
12 pub active_filter: Option<AutoChestFilter>,
13 pub filters: Vec<AutoChestFilter>,
14 pub power_compare_enabled: bool,
15 pub batch_size: i64,
16}
17
18impl Default for AutoChest {
19 fn default() -> Self {
20 Self {
21 active_filter: None,
22 filters: Vec::new(),
23 power_compare_enabled: true,
24 batch_size: 0,
25 }
26 }
27}
28
29#[declare]
30pub type AutoChestFilterId = Uuid;
31
32#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Tsify)]
33pub struct AutoChestFilter {
34 pub id: AutoChestFilterId,
35 pub name: String,
36 pub rarity_id: Option<ItemRarityId>,
37 pub guaranteed_attribute_enabled: bool,
38 pub guaranteed_attribute_id: Option<AttributeId>,
39 pub additional_attribute_enabled: bool,
40 pub additional_attribute_ids: Vec<AttributeId>,
41}
42
43impl AutoChestFilter {
44 pub fn validate(
45 &self,
46 cur_chapter_level: i64,
47 character_level: i64,
48 attributes: &[Attribute],
49 autochest_gatings: &AutoChestGatings,
50 ) -> anyhow::Result<()> {
51 if character_level < autochest_gatings.autochest_button_unlock_character_level {
56 anyhow::bail!(
57 "Auto chest is not enabled at character_level = {}",
58 character_level
59 )
60 }
61
62 if self.rarity_id.is_some() && cur_chapter_level < autochest_gatings.rarity_filter_unlock {
63 anyhow::bail!(
64 "Rarity filter is not available on cur_chapter_level = {}",
65 cur_chapter_level
66 )
67 }
68
69 if let Some(guaranteed_attribute_id) = self.guaranteed_attribute_id {
70 if cur_chapter_level < autochest_gatings.guaranteed_stat_unlock {
71 anyhow::bail!(
72 "Guaranteed stat filter is not available on cur_chapter_level = {}",
73 cur_chapter_level
74 )
75 }
76
77 let Some(guaranteed_attr) = attributes
78 .iter()
79 .find(|attr| attr.id == guaranteed_attribute_id)
80 else {
81 anyhow::bail!(
82 "Tried finding guaranteed_attribute with id = {}, but didn't find it in config",
83 guaranteed_attribute_id
84 )
85 };
86
87 if !guaranteed_attr.is_ui_visible {
88 anyhow::bail!(
89 "Provided guaranteed attribute = {:?}, shouldn't be in auto chest",
90 guaranteed_attr
91 )
92 }
93 }
94
95 if !self.additional_attribute_ids.is_empty() {
96 if self.additional_attribute_ids.len() > 3 {
97 anyhow::bail!(
98 "More additional attributes provided: {}, than allowed: 3",
99 self.additional_attribute_ids.len(),
100 )
101 }
102
103 let required_chapter = match self.additional_attribute_ids.len() {
104 1 => autochest_gatings.first_additional_stat_unlock,
105 2 => autochest_gatings.second_additional_stat_unlock,
106 3 => autochest_gatings.third_additional_stat_unlock,
107 _ => unreachable!(),
108 };
109
110 if cur_chapter_level < required_chapter {
111 anyhow::bail!(
112 "Additional stats filter with {} attribute(s) is not available on cur_chapter_level = {}",
113 self.additional_attribute_ids.len(),
114 cur_chapter_level
115 )
116 }
117
118 for additional_attr_id in &self.additional_attribute_ids {
119 let Some(additional_attr) = attributes
120 .iter()
121 .find(|attr| attr.id == *additional_attr_id)
122 else {
123 anyhow::bail!(
124 "Tried finding additional_attribute with id = {}, but didn't find it in config",
125 additional_attr_id
126 )
127 };
128
129 if !additional_attr.is_ui_visible {
130 anyhow::bail!(
131 "Provided additional attribute = {:?}, shouldn't be in auto chest",
132 additional_attr
133 )
134 }
135 }
136 }
137
138 Ok(())
139 }
140}
141
142pub fn filter_items(
143 items: &[Item],
144 filter: &Option<AutoChestFilter>,
145 power_compare_enabled: bool,
146 item_rarities: &Vec<ItemRarity>,
147 item_powers: &std::collections::HashMap<uuid::Uuid, i64>,
148 equipped_item_powers: &std::collections::HashMap<crate::items::ItemType, i64>,
149) -> anyhow::Result<(Vec<Item>, Vec<Item>)> {
150 let mut items_to_sell = vec![];
151 let mut items_to_equip = vec![];
152
153 let filter_rarity = if let Some(filter) = filter
154 && let Some(rarity_id) = filter.rarity_id
155 {
156 let Some(rarity) = item_rarities.iter().find(|r| r.id == rarity_id) else {
157 anyhow::bail!(
158 "Couldn't find filter rarity_id = {}, in provided item_rarities = {:?}",
159 rarity_id,
160 item_rarities
161 );
162 };
163 Some(rarity)
164 } else {
165 None
166 };
167
168 for item in items {
169 if let Some(filter_rarity) = filter_rarity
170 && item.rarity.order < filter_rarity.order
171 {
172 items_to_sell.push(item.clone());
173 continue;
174 }
175
176 if let Some(filter) = filter {
177 if filter.guaranteed_attribute_enabled
178 && let Some(guaranteed_attribute_id) = filter.guaranteed_attribute_id
179 && !item
180 .attributes
181 .iter()
182 .any(|item_attr| item_attr.attr_id == guaranteed_attribute_id)
183 {
184 items_to_sell.push(item.clone());
185 continue;
186 }
187
188 if filter.additional_attribute_enabled
189 && !filter.additional_attribute_ids.is_empty()
190 && !filter.additional_attribute_ids.iter().any(|id| {
191 item.attributes
192 .iter()
193 .any(|item_attr| *id == item_attr.attr_id)
194 })
195 {
196 items_to_sell.push(item.clone());
197 continue;
198 }
199 }
200
201 if power_compare_enabled
203 && let Some(&equipped_power) = equipped_item_powers.get(&item.item_type)
204 && let Some(&item_power) = item_powers.get(&item.id)
205 && item_power < equipped_power
206 {
207 items_to_sell.push(item.clone());
208 continue;
209 }
210
211 items_to_equip.push(item.clone());
212 }
213
214 Ok((items_to_sell, items_to_equip))
215}