Gacha System implementation #1

Merged
xeon merged 7 commits from YYHEggEgg/JaneDoe-ZS:master into master 2024-08-04 11:41:24 +00:00
7 changed files with 1968 additions and 60 deletions
Showing only changes of commit 3043d45ac9 - Show all commits

File diff suppressed because it is too large Load diff

View file

@ -126,11 +126,21 @@ pub struct MustGainItem {
pub category_tag: String, pub category_tag: String,
} }
#[derive(Debug, Default, Deserialize)]
pub struct FreeSelectItem {
pub milestones: Vec<u32>,
pub rarity: u32,
pub category_tags: Vec<String>,
pub free_select_progress_record_tag: String,
pub free_select_usage_record_tag: String,
}
#[derive(Debug, Default, Deserialize)] #[derive(Debug, Default, Deserialize)]
pub struct DiscountPolicyCollection { pub struct DiscountPolicyCollection {
pub ten_pull_discount_map: HashMap<String, TenPullDiscount>, pub ten_pull_discount_map: HashMap<String, TenPullDiscount>,
pub must_gain_item_map: HashMap<String, MustGainItem>, pub must_gain_item_map: HashMap<String, MustGainItem>,
pub advanced_guarantee_map: HashMap<String, AdvancedGuarantee>, pub advanced_guarantee_map: HashMap<String, AdvancedGuarantee>,
pub free_select_map: HashMap<String, FreeSelectItem>,
} }
impl DiscountPolicyCollection { impl DiscountPolicyCollection {

View file

@ -1,3 +1,11 @@
use std::{
cmp::min,
collections::{
hash_map::Entry::{Occupied, Vacant},
HashSet,
},
};
use data::{ use data::{
gacha::{gacha_config::*, global_gacha_config}, gacha::{gacha_config::*, global_gacha_config},
tables::{AvatarBaseID, WeaponID}, tables::{AvatarBaseID, WeaponID},
@ -24,7 +32,7 @@ pub async fn on_get_gacha_data(
Ok(GetGachaDataScRsp { Ok(GetGachaDataScRsp {
retcode: Retcode::RetSucc.into(), retcode: Retcode::RetSucc.into(),
gacha_type: req.gacha_type, gacha_type: req.gacha_type,
gacha_data: Some(generate_all_gacha_info(_player)), gacha_data: Some(generate_all_gacha_info(_player, &Local::now())),
}) })
} }
} }
@ -45,8 +53,12 @@ pub async fn on_do_gacha(
&pull_time, &pull_time,
); );
if let None = target_pool { if let None = target_pool {
tracing::info!(
"refuse gacha because: pool of parent_schedule_id {} not found",
req.gacha_parent_schedule_id
);
return Ok(DoGachaScRsp { return Ok(DoGachaScRsp {
retcode: Retcode::RetSucc.into(), retcode: Retcode::RetFail.into(),
..Default::default() ..Default::default()
}); });
}; };
@ -72,11 +84,15 @@ pub async fn on_do_gacha(
let usage = status_bin.discount_usage_map.get_mut(discount_tag).unwrap(); let usage = status_bin.discount_usage_map.get_mut(discount_tag).unwrap();
if *usage < discount_policy.use_limit { if *usage < discount_policy.use_limit {
*usage += 1; *usage += 1;
cost_count = discount_policy.discounted_prize; // cost_count = discount_policy.discounted_prize;
} }
} }
} }
if cost_count != req.cost_item_count { if cost_count != req.cost_item_count {
tracing::info!(
"refuse gacha because: expected cost item {cost_count}, found {}",
req.cost_item_count
);
return Ok(DoGachaScRsp { return Ok(DoGachaScRsp {
retcode: Retcode::RetFail.into(), retcode: Retcode::RetFail.into(),
..Default::default() ..Default::default()
@ -88,7 +104,7 @@ pub async fn on_do_gacha(
let mut gain_item_list: Vec<GainItemInfo> = vec![]; let mut gain_item_list: Vec<GainItemInfo> = vec![];
while pull_count > 0 { while pull_count > 0 {
let pull_result = gacha_model.perform_pull_pool(&pull_time, target_pool); let pull_result = gacha_model.perform_pull_pool(&pull_time, target_pool);
let extra_item_bin = pull_result.extra_item_bin.unwrap(); let extra_item_bin = pull_result.extra_item_bin.clone().unwrap();
let uid = match GachaAddedItemType::try_from(pull_result.item_type) { let uid = match GachaAddedItemType::try_from(pull_result.item_type) {
Ok(enum_val) => match enum_val { Ok(enum_val) => match enum_val {
GachaAddedItemType::Weapon => match WeaponID::new(pull_result.obtained_item_id) { GachaAddedItemType::Weapon => match WeaponID::new(pull_result.obtained_item_id) {
@ -123,16 +139,236 @@ pub async fn on_do_gacha(
..GainItemInfo::default() ..GainItemInfo::default()
}); });
pull_count -= 1; pull_count -= 1;
gacha_model.gacha_bin.gacha_records.push(pull_result);
} }
Ok(DoGachaScRsp { Ok(DoGachaScRsp {
retcode: Retcode::RetSucc.into(), retcode: Retcode::RetSucc.into(),
gain_item_list, gain_item_list,
gacha_data: Some(generate_all_gacha_info(_player)), gacha_data: Some(generate_all_gacha_info(_player, &pull_time)),
cost_item_count: req.cost_item_count, cost_item_count: req.cost_item_count,
}) })
} }
pub async fn on_gacha_free_agent(
_session: &NetSession,
_player: &mut Player,
req: GachaFreeAgentCsReq,
) -> NetResult<GachaFreeAgentScRsp> {
let gachaconf = global_gacha_config();
let gacha_model = &mut _player.gacha_model;
let role_model = &mut _player.role_model;
let pull_time = Local::now();
let target_pool = get_gacha_pool(
&gachaconf.character_gacha_pool_list,
&req.gacha_parent_schedule_id,
&pull_time,
);
if let None = target_pool {
tracing::info!(
"refuse free agent because: pool of parent_schedule_id {} not found",
req.gacha_parent_schedule_id
);
return Ok(GachaFreeAgentScRsp {
retcode: Retcode::RetFail.into(),
..Default::default()
});
};
let target_pool = target_pool.unwrap();
let gacha_bin = &mut _player.gacha_model.gacha_bin;
let sharing_guarantee_category_tag = &target_pool.sharing_guarantee_info_category;
let status_bin = gacha_bin
.gacha_status_map
.get_mut(sharing_guarantee_category_tag)
.unwrap();
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)",
req.gacha_parent_schedule_id
);
return Ok(GachaFreeAgentScRsp {
retcode: Retcode::RetFail.into(),
..Default::default()
});
} 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}",
req.gacha_parent_schedule_id
);
return Ok(GachaFreeAgentScRsp {
retcode: Retcode::RetFail.into(),
..Default::default()
});
}
let free_select_policy = free_select_policy.unwrap();
let mut has_demanded_item = false;
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;
}
has_demanded_item |= category.item_ids.contains(&req.avatar_id);
}
}
if !has_demanded_item {
tracing::info!(
"refuse free agent because: pool of parent_schedule_id {} doesn't have demanded item {}",
req.gacha_parent_schedule_id, req.avatar_id
);
return Ok(GachaFreeAgentScRsp {
retcode: Retcode::RetFail.into(),
..Default::default()
});
}
role_model.add_avatar(AvatarBaseID::new(req.avatar_id).unwrap());
(*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;
Ok(GachaFreeAgentScRsp {
retcode: Retcode::RetSucc.into(),
})
}
pub async fn on_choose_gacha_up(
_session: &NetSession,
_player: &mut Player,
req: ChooseGachaUpCsReq,
) -> NetResult<ChooseGachaUpScRsp> {
let gachaconf = global_gacha_config();
let gacha_model = &mut _player.gacha_model;
let pull_time = Local::now();
let target_pool = get_gacha_pool(
&gachaconf.character_gacha_pool_list,
&req.gacha_parent_schedule_id,
&pull_time,
);
if let None = target_pool {
return Ok(ChooseGachaUpScRsp {
retcode: Retcode::RetFail.into(),
..Default::default()
});
};
let target_pool = target_pool.unwrap();
for rarity_items in target_pool.gacha_items.iter() {
if rarity_items.rarity != gachaconf.common_properties.s_item_rarity {
continue;
}
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(&req.item_id) {
up_category = Some(category_tag);
break;
}
}
if let None = up_category {
return Ok(ChooseGachaUpScRsp {
retcode: Retcode::RetFail.into(),
..Default::default()
});
};
let up_category = up_category.unwrap();
let progress_bin = gacha_model
.gacha_bin
.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(req.item_id);
}
Vacant(vacant_entry) => {
vacant_entry.insert(req.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 Ok(ChooseGachaUpScRsp {
retcode: Retcode::RetSucc.into(),
..Default::default()
});
}
}
Ok(ChooseGachaUpScRsp {
retcode: Retcode::RetFail.into(),
..Default::default()
})
}
fn generate_gacha_info_from_pool( fn generate_gacha_info_from_pool(
gacha_bin: &GachaModelBin, gacha_bin: &GachaModelBin,
target_pool: &CharacterGachaPool, target_pool: &CharacterGachaPool,
@ -154,34 +390,13 @@ fn generate_gacha_info_from_pool(
.get(&common_properties.a_item_rarity) .get(&common_properties.a_item_rarity)
.unwrap() .unwrap()
.pity; .pity;
let mut up_s_item_list: Vec<u32> = vec![];
let mut up_a_item_list: Vec<u32> = vec![];
let mut s_guarantee: u32 = 0;
let mut a_guarantee: u32 = 0;
for rarity_items in target_pool.gacha_items.iter() {
for category in rarity_items.categories.values() {
let probability_model = gachaconf
.probability_model_map
.get(&rarity_items.probability_model_tag)
.unwrap();
if rarity_items.rarity == common_properties.s_item_rarity {
if category.is_promotional_items {
up_s_item_list = category.item_ids.clone();
}
s_guarantee = probability_model.maximum_guarantee_pity - pity_s + 1;
}
if rarity_items.rarity == common_properties.a_item_rarity {
if category.is_promotional_items {
up_a_item_list = category.item_ids.clone();
}
a_guarantee = probability_model.maximum_guarantee_pity - pity_a + 1;
}
}
}
let mut discount_ten_roll_prize: u32 = 0; let mut discount_ten_roll_prize: u32 = 0;
let mut discount_avaliable_num: u32 = 0; let mut discount_avaliable_num: u32 = 0;
let mut advanced_s_guarantee: 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() { for discount_policy_tag in target_pool.discount_policy_tags.iter() {
if common_properties.newcomer_advanced_s_tag == *discount_policy_tag { if common_properties.newcomer_advanced_s_tag == *discount_policy_tag {
let policy = gachaconf let policy = gachaconf
@ -211,6 +426,123 @@ fn generate_gacha_info_from_pool(
discount_ten_roll_prize = policy.discounted_prize; discount_ten_roll_prize = policy.discounted_prize;
discount_avaliable_num = policy.use_limit - discount_usage; 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<u32> = vec![];
let mut up_a_item_list: Vec<u32> = vec![];
let mut free_select_item_list: Vec<u32> = vec![];
let mut chooseable_up_list: Vec<u32> = 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<String>> = 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 = vec![];
} else if rarity_items.rarity == gachaconf.common_properties.a_item_rarity {
up_a_item_list = vec![];
}
// }
} }
} }
@ -219,7 +551,7 @@ fn generate_gacha_info_from_pool(
need_item_count: 1, need_item_count: 1,
}]; }];
Gacha { let mut result = Gacha {
gacha_schedule_id: target_pool.gacha_schedule_id, gacha_schedule_id: target_pool.gacha_schedule_id,
gacha_parent_schedule_id: target_pool.gacha_parent_schedule_id, gacha_parent_schedule_id: target_pool.gacha_parent_schedule_id,
gacha_type: target_pool.gacha_type, gacha_type: target_pool.gacha_type,
@ -227,34 +559,46 @@ fn generate_gacha_info_from_pool(
end_timestamp: target_pool.end_time.timestamp(), end_timestamp: target_pool.end_time.timestamp(),
discount_avaliable_num, discount_avaliable_num,
discount_ten_roll_prize, discount_ten_roll_prize,
up_s_item_list,
up_a_item_list,
advanced_s_guarantee, advanced_s_guarantee,
s_guarantee, s_guarantee,
a_guarantee, a_guarantee,
need_item_info_list, need_item_info_list,
lpklhoobkbh: target_pool.gacha_parent_schedule_id, free_select_progress,
nammdglepbk: 593, free_select_required_pull,
hgmcofcjmbg: 101, free_select_item_list,
chosen_up_item,
// nammdglepbk: 563,
// hgmcofcjmbg: 101,
// akggbhgkifd: chooseable_up_list.clone(),
chooseable_up_list,
..Gacha::default() ..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
} }
fn generate_all_gacha_info(_player: &Player) -> GachaData { fn generate_all_gacha_info(_player: &Player, now: &DateTime<Local>) -> GachaData {
let gachaconf = global_gacha_config(); let gachaconf = global_gacha_config();
let gacha_bin = &_player.gacha_model.gacha_bin; let gacha_bin = &_player.gacha_model.gacha_bin;
let mut gacha_list: Vec<Gacha> = vec![]; let mut gacha_list: Vec<Gacha> = vec![];
for target_pool in gachaconf.character_gacha_pool_list.iter() { for target_pool in gachaconf.character_gacha_pool_list.iter() {
gacha_list.push(generate_gacha_info_from_pool( if target_pool.is_still_open(now) {
&gacha_bin, gacha_list.push(generate_gacha_info_from_pool(
target_pool, &gacha_bin,
&gachaconf.common_properties, target_pool,
)); &gachaconf.common_properties,
));
}
} }
// tracing::info!("gacha_list: {:?}", gacha_list); // tracing::info!("gacha_list: {:?}", gacha_list);
GachaData { GachaData {
random_number: 0, random_number: 6167,
gacha_pool: Some(GachaPool { gacha_list }), gacha_pool: Some(GachaPool { gacha_list }),
..GachaData::default() ..GachaData::default()
} }

View file

@ -120,6 +120,8 @@ req_handlers! {
quest::BeginArchiveBattleQuest; quest::BeginArchiveBattleQuest;
quest::FinishArchiveQuest; quest::FinishArchiveQuest;
gacha::DoGacha; gacha::DoGacha;
gacha::ChooseGachaUp;
gacha::GachaFreeAgent;
} }
notify_handlers! { notify_handlers! {

View file

@ -52,10 +52,42 @@ impl GachaModel {
&mut progress_bin.categories_progress_map, &mut progress_bin.categories_progress_map,
&category_guarantee_policy_tag, &category_guarantee_policy_tag,
); );
let guarantee_policy = gachaconf
.category_guarantee_policy_map
.get(category_guarantee_policy_tag)
.unwrap();
if !guarantee_policy.chooseable {
continue;
}
get_or_add(
&mut progress_bin.categories_chosen_guarantee_progress_map,
&category_guarantee_policy_tag,
);
} }
} }
for discount_policy_tag in gacha_pool.discount_policy_tags.iter() { for discount_policy_tag in gacha_pool.discount_policy_tags.iter() {
get_or_add(&mut status_bin.discount_usage_map, &discount_policy_tag); 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();
get_or_add(
&mut status_bin.discount_usage_map,
&policy.free_select_progress_record_tag,
);
get_or_add(
&mut status_bin.discount_usage_map,
&policy.free_select_usage_record_tag,
);
} else {
get_or_add(&mut status_bin.discount_usage_map, &discount_policy_tag);
}
} }
} }
self self
@ -70,8 +102,14 @@ impl GachaModel {
let (rarity_items, progress_bin, status_bin, probability_model) = let (rarity_items, progress_bin, status_bin, probability_model) =
determine_rarity(&gacha_bin, target_pool); determine_rarity(&gacha_bin, target_pool);
let (category_tag, category) = determine_category(rarity_items, progress_bin, target_pool); let (category_tag, category) = determine_category(rarity_items, progress_bin, target_pool);
let result = let result = determine_gacha_result(
determine_gacha_result(pull_time, category, target_pool, status_bin, rarity_items); pull_time,
category,
target_pool,
status_bin,
progress_bin,
rarity_items,
);
update_pity(&mut gacha_bin, rarity_items, probability_model, target_pool); update_pity(&mut gacha_bin, rarity_items, probability_model, target_pool);
update_category_guarantee_info(&mut gacha_bin, rarity_items, &category_tag, target_pool); update_category_guarantee_info(&mut gacha_bin, rarity_items, &category_tag, target_pool);
update_discount(&mut gacha_bin, target_pool, &category_tag, rarity_items); update_discount(&mut gacha_bin, target_pool, &category_tag, rarity_items);
@ -196,9 +234,27 @@ fn determine_category<'bin, 'conf>(
let mut category_tag_inited = false; let mut category_tag_inited = false;
let mut category_tag_result: HashSet<String> = HashSet::new(); let mut category_tag_result: HashSet<String> = HashSet::new();
// First of all, if there's a chooseable category and // First of all, if there's a chooseable category and
// it's can be triggered, then we MUST give that // it is SELECTED then we MUST give that category's item.
// category's item. for guarantee_policy_tag in rarity_items.category_guarantee_policy_tags.iter() {
// TODO: Only Genshin can do let category_guarantee_policy = gachaconf
.category_guarantee_policy_map
.get(guarantee_policy_tag)
.unwrap();
if !category_guarantee_policy.chooseable {
continue;
}
// As we found a policy defined chooseable, we
// should head to look whether the user chose
// the category he want.
if let Some(category_tag) = progress_bin
.categories_chosen_guarantee_category_map
.get(guarantee_policy_tag)
{
// User chose a category; our work are done here.
category_tag_result.insert(category_tag.clone());
category_tag_inited = true;
}
}
// Then we should take a look at MustGainItem. // Then we should take a look at MustGainItem.
if !category_tag_inited { if !category_tag_inited {
for discount_policy_tag in target_pool.discount_policy_tags.iter() { for discount_policy_tag in target_pool.discount_policy_tags.iter() {
@ -262,14 +318,49 @@ fn determine_gacha_result<'bin, 'conf>(
category: &'conf GachaCategoryInfo, category: &'conf GachaCategoryInfo,
target_pool: &'conf CharacterGachaPool, target_pool: &'conf CharacterGachaPool,
status_bin: &'bin GachaStatusBin, status_bin: &'bin GachaStatusBin,
progress_bin: &'bin GachaProgressBin,
rarity_items: &'conf GachaAvailableItemsInfo, rarity_items: &'conf GachaAvailableItemsInfo,
) -> GachaRecordBin { ) -> GachaRecordBin {
let gachaconf = gacha::global_gacha_config(); let gachaconf = gacha::global_gacha_config();
let item_pool_len = category.item_ids.len() as u32; let item_pool_len = category.item_ids.len() as u32;
let item_id = category let mut item_id: Option<&u32> = None;
.item_ids // We should see whether user's search priority exists.
.get(rand::thread_rng().gen_range(0..item_pool_len) as usize) for guarantee_policy_tag in rarity_items.category_guarantee_policy_tags.iter() {
.unwrap(); let category_guarantee_policy = gachaconf
.category_guarantee_policy_map
.get(guarantee_policy_tag)
.unwrap();
if !category_guarantee_policy.chooseable {
continue;
}
// Firstly, judge whether the user failed enough times.
// The user is limited to get only this category's item,
// so we should record the user's failure to get his
// selected item elsewhere.
if progress_bin
.categories_chosen_guarantee_progress_map
.get(guarantee_policy_tag)
.unwrap()
< &category_guarantee_policy.trigger_on_failure_times
{
continue;
}
// We directly look whether user chose an UP item.
if let Some(item) = progress_bin
.categories_chosen_guarantee_item_map
.get(guarantee_policy_tag)
{
item_id = Some(item);
}
}
let item_id = match item_id {
Some(val) => val,
None => category
.item_ids
.get(rand::thread_rng().gen_range(0..item_pool_len) as usize)
.unwrap(),
};
let mut extra_item_id: u32 = 0; let mut extra_item_id: u32 = 0;
let mut extra_item_count: u32 = 0; let mut extra_item_count: u32 = 0;
@ -279,6 +370,7 @@ fn determine_gacha_result<'bin, 'conf>(
.get(extra_items_policy_tag) .get(extra_items_policy_tag)
.unwrap(); .unwrap();
// TODO: apply_on_owned_count in a context with bag // TODO: apply_on_owned_count in a context with bag
// TODO: That's what RoleModel should do, not me.
if extra_items_policy.apply_on_owned_count == 0 { if extra_items_policy.apply_on_owned_count == 0 {
extra_item_id = extra_items_policy.id; extra_item_id = extra_items_policy.id;
extra_item_count = extra_items_policy.count; extra_item_count = extra_items_policy.count;
@ -396,4 +488,18 @@ fn update_discount<'bin, 'conf>(
*usage += 1; *usage += 1;
} }
} }
for (policy_tag, policy) in gachaconf.discount_policies.free_select_map.iter() {
if !target_pool.discount_policy_tags.contains(policy_tag) {
continue;
}
let status_bin = gacha_bin
.gacha_status_map
.get_mut(&target_pool.sharing_guarantee_info_category)
.unwrap();
let progress = status_bin
.discount_usage_map
.get_mut(&policy.free_select_progress_record_tag)
.unwrap();
*progress += 1;
}
} }

View file

@ -6844,7 +6844,7 @@ pub struct Pofhbffcjap {
#[derive(proto_gen::XorFields)] #[derive(proto_gen::XorFields)]
#[allow(clippy::derive_partial_eq_without_eq)] #[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)] #[derive(Clone, PartialEq, ::prost::Message)]
pub struct Nhffnnompfh { pub struct ChooseGachaUpScRsp {
#[xor(4109)] #[xor(4109)]
#[prost(int32, tag = "2")] #[prost(int32, tag = "2")]
pub retcode: i32, pub retcode: i32,
@ -17992,7 +17992,7 @@ pub mod gbcnocdacgf {
#[prost(message, tag = "12")] #[prost(message, tag = "12")]
Summonee(super::Endadpkgkid), Summonee(super::Endadpkgkid),
#[prost(message, tag = "13")] #[prost(message, tag = "13")]
BuddyId(super::Ecjcmfjjgdp), Buddy(super::Ecjcmfjjgdp),
#[prost(message, tag = "14")] #[prost(message, tag = "14")]
DropItem(super::Mfbjkggafmo), DropItem(super::Mfbjkggafmo),
#[prost(message, tag = "15")] #[prost(message, tag = "15")]
@ -22295,7 +22295,7 @@ pub struct Gacha {
pub gacha_info_list_webview: ::prost::alloc::string::String, pub gacha_info_list_webview: ::prost::alloc::string::String,
#[xor(9009)] #[xor(9009)]
#[prost(uint32, tag = "419")] #[prost(uint32, tag = "419")]
pub ekjlhhdekka: u32, pub chosen_up_item: u32,
#[prost(string, tag = "923")] #[prost(string, tag = "923")]
pub fjohnbicmce: ::prost::alloc::string::String, pub fjohnbicmce: ::prost::alloc::string::String,
#[xor(1379)] #[xor(1379)]
@ -24558,7 +24558,7 @@ pub struct Obpccjhnbpe {
#[derive(proto_gen::XorFields)] #[derive(proto_gen::XorFields)]
#[allow(clippy::derive_partial_eq_without_eq)] #[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)] #[derive(Clone, PartialEq, ::prost::Message)]
pub struct Pahjnbjogon { pub struct ChooseGachaUpCsReq {
#[xor(3180)] #[xor(3180)]
#[prost(uint32, tag = "6")] #[prost(uint32, tag = "6")]
pub item_id: u32, pub item_id: u32,
@ -27531,7 +27531,7 @@ pub struct TipsInfo {
#[derive(proto_gen::XorFields)] #[derive(proto_gen::XorFields)]
#[allow(clippy::derive_partial_eq_without_eq)] #[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)] #[derive(Clone, PartialEq, ::prost::Message)]
pub struct Ieimfkpmegp { pub struct GachaFreeAgentCsReq {
#[xor(378)] #[xor(378)]
#[prost(uint32, tag = "12")] #[prost(uint32, tag = "12")]
pub gacha_parent_schedule_id: u32, pub gacha_parent_schedule_id: u32,
@ -31687,7 +31687,7 @@ pub struct Amhlhmjgcpk {
#[derive(proto_gen::XorFields)] #[derive(proto_gen::XorFields)]
#[allow(clippy::derive_partial_eq_without_eq)] #[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)] #[derive(Clone, PartialEq, ::prost::Message)]
pub struct Idonpiailid { pub struct GachaFreeAgentScRsp {
#[xor(3883)] #[xor(3883)]
#[prost(int32, tag = "3")] #[prost(int32, tag = "3")]
pub retcode: i32, pub retcode: i32,

View file

@ -117,6 +117,7 @@ pub struct MainCityModelBin {
#[prost(uint32, tag = "3")] #[prost(uint32, tag = "3")]
pub section_id: u32, pub section_id: u32,
} }
/// The progress record of a specified rarity. All maps' keys are category_guarantee_policy_tag.
#[allow(clippy::derive_partial_eq_without_eq)] #[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)] #[derive(Clone, PartialEq, ::prost::Message)]
pub struct GachaProgressBin { pub struct GachaProgressBin {
@ -129,12 +130,24 @@ pub struct GachaProgressBin {
::prost::alloc::string::String, ::prost::alloc::string::String,
u32, u32,
>, >,
/// The selected priority for a Chooseable category. /// The selected priority (category) for a Chooseable category.
#[prost(map = "string, string", tag = "3")] #[prost(map = "string, string", tag = "3")]
pub category_chosen_guarantee_map: ::std::collections::HashMap< pub categories_chosen_guarantee_category_map: ::std::collections::HashMap<
::prost::alloc::string::String, ::prost::alloc::string::String,
::prost::alloc::string::String, ::prost::alloc::string::String,
>, >,
/// The selectedpriority (a specified item) for a Chooseable category.
#[prost(map = "string, uint32", tag = "4")]
pub categories_chosen_guarantee_item_map: ::std::collections::HashMap<
::prost::alloc::string::String,
u32,
>,
/// The failure times for selected priority (a specified item).
#[prost(map = "string, uint32", tag = "5")]
pub categories_chosen_guarantee_progress_map: ::std::collections::HashMap<
::prost::alloc::string::String,
u32,
>,
} }
#[allow(clippy::derive_partial_eq_without_eq)] #[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)] #[derive(Clone, PartialEq, ::prost::Message)]