use data::gacha::gacha_config::*; use data::gacha::global_gacha_config; use data::tables::ItemID; use chrono::{DateTime, Local}; use proto::{Gacha, GachaData, GachaPool, NeedItemInfo}; use std::{ cmp::min, collections::{ hash_map::Entry::{Occupied, Vacant}, HashSet, }, }; use super::GachaModel; impl GachaModel { pub fn to_client(&self, now: &DateTime) -> GachaData { let gachaconf = global_gacha_config(); let mut gacha_list: Vec = vec![]; for target_pool in gachaconf.character_gacha_pool_list.iter() { if target_pool.is_still_open(now) { gacha_list.push( self.generate_gacha_info_from_pool(target_pool, &gachaconf.common_properties), ); } } // tracing::info!("gacha_list: {:?}", gacha_list); GachaData { random_number: 6167, gacha_pool: Some(GachaPool { gacha_list }), ..GachaData::default() } } fn generate_gacha_info_from_pool( &self, target_pool: &CharacterGachaPool, common_properties: &GachaCommonProperties, ) -> Gacha { let gachaconf = data::gacha::global_gacha_config(); let sharing_guarantee_category_tag = &target_pool.sharing_guarantee_info_category; let status_bin = self .gacha_status_map .get(sharing_guarantee_category_tag) .unwrap(); let pity_s = status_bin .rarity_status_map .get(&common_properties.s_item_rarity) .unwrap() .pity; let pity_a = status_bin .rarity_status_map .get(&common_properties.a_item_rarity) .unwrap() .pity; let mut discount_ten_roll_prize: u32 = 0; let mut discount_avaliable_num: u32 = 0; let mut advanced_s_guarantee: u32 = 0; let mut free_select_progress: u32 = 0; let mut free_select_required_pull: u32 = 0; let mut free_select_policy: Option<&FreeSelectItem> = None; for discount_policy_tag in target_pool.discount_policy_tags.iter() { if common_properties.newcomer_advanced_s_tag == *discount_policy_tag { let policy = gachaconf .discount_policies .advanced_guarantee_map .get(discount_policy_tag) .unwrap(); if status_bin .discount_usage_map .get(discount_policy_tag) .unwrap() < &policy.use_limit { advanced_s_guarantee = policy.guarantee_pity - pity_s + 1; } } else if common_properties.ten_pull_discount_tag == *discount_policy_tag { let policy = gachaconf .discount_policies .ten_pull_discount_map .get(discount_policy_tag) .unwrap(); let discount_usage = status_bin .discount_usage_map .get(discount_policy_tag) .unwrap(); if discount_usage < &policy.use_limit { discount_ten_roll_prize = policy.discounted_prize; discount_avaliable_num = policy.use_limit - discount_usage; } } else if gachaconf .discount_policies .free_select_map .contains_key(discount_policy_tag) { let policy = gachaconf .discount_policies .free_select_map .get(discount_policy_tag) .unwrap(); let free_select_demand_idx = usize::try_from( *(status_bin .discount_usage_map .get(&policy.free_select_usage_record_tag) .unwrap()), ) .unwrap(); if policy.milestones.len() <= free_select_demand_idx { continue; } let free_select_actual_progress = status_bin .discount_usage_map .get(&policy.free_select_progress_record_tag) .unwrap(); free_select_policy = Some(policy); free_select_required_pull = policy .milestones .get(free_select_demand_idx) .unwrap() .to_owned(); free_select_progress = min(free_select_required_pull, *free_select_actual_progress); } } let mut up_s_item_list: Vec = vec![]; let mut up_a_item_list: Vec = vec![]; let mut free_select_item_list: Vec = vec![]; let mut chooseable_up_list: Vec = vec![]; let mut chosen_up_item: u32 = 0; let mut s_guarantee: u32 = 0; let mut a_guarantee: u32 = 0; for rarity_items in target_pool.gacha_items.iter() { let mut chooseable_up_included_category_tags: Option<&HashSet> = None; let mut chooseable_policy_tag: Option<&String> = None; for guarantee_policy_tag in rarity_items.category_guarantee_policy_tags.iter() { let category_guarantee_policy = gachaconf .category_guarantee_policy_map .get(guarantee_policy_tag) .unwrap(); if !category_guarantee_policy.chooseable { continue; } chooseable_policy_tag = Some(guarantee_policy_tag); chooseable_up_included_category_tags = Some(&category_guarantee_policy.included_category_tags); if let Some(item) = status_bin .rarity_status_map .get(&rarity_items.rarity) .unwrap() .categories_chosen_guarantee_item_map .get(guarantee_policy_tag) { chosen_up_item = item.clone(); } } for (category_tag, category) in rarity_items.categories.iter() { let probability_model = gachaconf .probability_model_map .get(&rarity_items.probability_model_tag) .unwrap(); let maximum_pity = &probability_model.maximum_guarantee_pity; if rarity_items.rarity == common_properties.s_item_rarity { if category.is_promotional_items { up_s_item_list = category.item_ids.clone(); } // tracing::info!("pity_s: {pity_s}"); // thread 'tokio-runtime-worker' panicked at nap_gameserver\src\handlers\gacha.rs:369:31: // attempt to subtract with overflow s_guarantee = maximum_pity - min(pity_s, maximum_pity.clone()) + 1; } if rarity_items.rarity == common_properties.a_item_rarity { if category.is_promotional_items { up_a_item_list = category.item_ids.clone(); } // tracing::info!("pity_a: {pity_a}"); a_guarantee = maximum_pity - min(pity_a, maximum_pity.clone()) + 1; } if let Some(val) = free_select_policy { if val.rarity == rarity_items.rarity && val.category_tags.contains(category_tag) { free_select_item_list.append(&mut category.item_ids.clone()); } } if let Some(tags) = chooseable_up_included_category_tags { if tags.contains(category_tag) { chooseable_up_list.append(&mut category.item_ids.clone()); } } } if let Some(_priority_policy_tag) = chooseable_policy_tag { // if let Some(item) = status_bin // .rarity_status_map // .get(&rarity_items.rarity) // .unwrap() // .categories_chosen_guarantee_item_map // .get(priority_policy_tag) // { if rarity_items.rarity == gachaconf.common_properties.s_item_rarity { up_s_item_list = chooseable_up_list.clone(); } else if rarity_items.rarity == gachaconf.common_properties.a_item_rarity { up_a_item_list = vec![]; } // } } } let need_item_info_list: Vec = vec![NeedItemInfo { need_item_id: target_pool.cost_item_id, need_item_count: 1, }]; let mut result = Gacha { gacha_schedule_id: target_pool.gacha_schedule_id, gacha_parent_schedule_id: target_pool.gacha_parent_schedule_id, gacha_type: target_pool.gacha_type, start_timestamp: target_pool.start_time.timestamp(), end_timestamp: target_pool.end_time.timestamp(), discount_avaliable_num, discount_ten_roll_prize, advanced_s_guarantee, s_guarantee, a_guarantee, need_item_info_list, free_select_progress, free_select_required_pull, free_select_item_list, chosen_up_item, // nammdglepbk: 563, // hgmcofcjmbg: 101, // akggbhgkifd: chooseable_up_list.clone(), chooseable_up_list, ..Gacha::default() }; if up_s_item_list.len() > 0 { result.up_s_item_list = up_s_item_list; } if up_a_item_list.len() > 0 { result.up_a_item_list = up_a_item_list; } result } /// Get the actual item cost count (counting discount). pub fn get_actual_cost_count<'bin, 'conf>( &'bin mut self, target_pool: &'conf CharacterGachaPool, pull_count: &u32, ) -> u32 { let gachaconf = global_gacha_config(); if *pull_count == 10 { let discount_tag = &gachaconf.common_properties.ten_pull_discount_tag; if target_pool.discount_policy_tags.contains(&discount_tag) { let status_bin = self .gacha_status_map .get_mut(&target_pool.sharing_guarantee_info_category) .unwrap(); let discount_policy = gachaconf .discount_policies .ten_pull_discount_map .get(discount_tag) .unwrap(); let usage = status_bin.discount_usage_map.get_mut(discount_tag).unwrap(); if *usage < discount_policy.use_limit { *usage += 1; return discount_policy.discounted_prize; } } } pull_count.clone() } pub fn request_free_agent<'bin, 'conf>( &'bin mut self, target_pool: &'conf CharacterGachaPool, item_id: &ItemID, ) -> GachaAddedItemType { let gachaconf = global_gacha_config(); let sharing_guarantee_category_tag = &target_pool.sharing_guarantee_info_category; let status_bin = self .gacha_status_map .get_mut(sharing_guarantee_category_tag) .unwrap(); let item_id = item_id.value(); let mut free_select_policy: Option<&FreeSelectItem> = None; let mut free_select_progress: u32 = 0; let mut free_select_required_pull: u32 = 0; for discount_policy_tag in target_pool.discount_policy_tags.iter() { if gachaconf .discount_policies .free_select_map .contains_key(discount_policy_tag) { let policy = gachaconf .discount_policies .free_select_map .get(discount_policy_tag) .unwrap(); let free_select_demand_idx = usize::try_from( *(status_bin .discount_usage_map .get(&policy.free_select_usage_record_tag) .unwrap()), ) .unwrap(); if policy.milestones.len() <= free_select_demand_idx { continue; } let free_select_actual_progress = status_bin .discount_usage_map .get(&policy.free_select_progress_record_tag) .unwrap(); free_select_policy = Some(policy); free_select_required_pull = policy .milestones .get(free_select_demand_idx) .unwrap() .to_owned(); free_select_progress = min(free_select_required_pull, *free_select_actual_progress); } } if let None = free_select_policy { tracing::info!( "refuse free agent because: pool of parent_schedule_id {} hasn't defined free agent discount yet (or used up chance)", target_pool.gacha_parent_schedule_id ); return GachaAddedItemType::None; } else if free_select_progress < free_select_required_pull { tracing::info!( "refuse free agent because: use pulled {free_select_progress} (after last free agent) in parent_schedule_id {}, required {free_select_required_pull}", target_pool.gacha_parent_schedule_id ); return GachaAddedItemType::None; } let free_select_policy = free_select_policy.unwrap(); let mut item_type: GachaAddedItemType = GachaAddedItemType::None; for rarity_items in target_pool.gacha_items.iter() { if rarity_items.rarity != free_select_policy.rarity { continue; } for (category_tag, category) in rarity_items.categories.iter() { if !free_select_policy.category_tags.contains(category_tag) { continue; } if category.item_ids.contains(&item_id) { item_type = category.item_type.clone(); } } } if item_type != GachaAddedItemType::None { (*status_bin .discount_usage_map .get_mut(&free_select_policy.free_select_usage_record_tag) .unwrap()) += 1; (*status_bin .discount_usage_map .get_mut(&free_select_policy.free_select_progress_record_tag) .unwrap()) -= free_select_required_pull; } item_type } pub fn choose_gacha_up<'bin, 'conf>( &'bin mut self, target_pool: &'conf CharacterGachaPool, item_id: &ItemID, ) -> bool { let gachaconf = global_gacha_config(); let item_id = item_id.value(); for rarity_items in target_pool.gacha_items.iter() { for guarantee_policy_tag in rarity_items.category_guarantee_policy_tags.iter() { let category_guarantee_policy = gachaconf .category_guarantee_policy_map .get(guarantee_policy_tag) .unwrap(); if !category_guarantee_policy.chooseable { continue; } let mut up_category: Option<&String> = None; for (category_tag, category) in rarity_items.categories.iter() { if category.item_ids.contains(&item_id) { up_category = Some(category_tag); break; } } if let None = up_category { continue; }; let up_category = up_category.unwrap(); let progress_bin = self .gacha_status_map .get_mut(&target_pool.sharing_guarantee_info_category) .unwrap() .rarity_status_map .get_mut(&rarity_items.rarity) .unwrap(); match progress_bin .categories_chosen_guarantee_item_map .entry(guarantee_policy_tag.clone()) { Occupied(mut occupied_entry) => { occupied_entry.insert(item_id); } Vacant(vacant_entry) => { vacant_entry.insert(item_id); } }; match progress_bin .categories_chosen_guarantee_category_map .entry(up_category.clone()) { Occupied(mut occupied_entry) => { occupied_entry.insert(up_category.clone()); } Vacant(vacant_entry) => { vacant_entry.insert(up_category.clone()); } }; return true; } } false } }