forked from NewEriduPubSec/JaneDoe-ZS
Compare commits
No commits in common. "2041d8dd03dc16f42f2bd3bc04b3f98533274a4e" and "970dde020a9f2b55855cb065cb6466f784a95d8a" have entirely different histories.
2041d8dd03
...
970dde020a
16 changed files with 68 additions and 1046 deletions
|
@ -18,5 +18,3 @@ tracing.workspace = true
|
||||||
|
|
||||||
# Internal
|
# Internal
|
||||||
proto.workspace = true
|
proto.workspace = true
|
||||||
jsonc-parser = { version = "0.23.0", features = ["serde"] }
|
|
||||||
chrono = { version = "0.4.38", features = ["serde"] }
|
|
||||||
|
|
|
@ -1,229 +0,0 @@
|
||||||
use std::collections::{HashMap, HashSet};
|
|
||||||
|
|
||||||
use chrono::{prelude::Local, DateTime};
|
|
||||||
use serde::Deserialize;
|
|
||||||
use tracing;
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize)]
|
|
||||||
pub struct ExtraItemsPolicy {
|
|
||||||
pub id: u32,
|
|
||||||
pub count: u32,
|
|
||||||
#[serde(default)]
|
|
||||||
pub apply_on_owned_count: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize)]
|
|
||||||
pub struct ProbabilityPoint {
|
|
||||||
pub start_pity: u32,
|
|
||||||
pub start_chance_percent: f64,
|
|
||||||
#[serde(default)]
|
|
||||||
pub increment_percent: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize)]
|
|
||||||
pub struct ProbabilityModel {
|
|
||||||
#[serde(default)]
|
|
||||||
pub clear_status_on_higher_rarity_pulled: bool,
|
|
||||||
pub points: Vec<ProbabilityPoint>,
|
|
||||||
// This value is for display only, so it's set when
|
|
||||||
// the maximum guarantee is not equal to the
|
|
||||||
// automatically calculated value (commonly, less than).
|
|
||||||
#[serde(default)]
|
|
||||||
pub maximum_guarantee_pity: u32,
|
|
||||||
|
|
||||||
#[serde(skip_deserializing)]
|
|
||||||
probability_percents: Vec<f64>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ProbabilityModel {
|
|
||||||
fn get_maximum_guarantee(&self) -> u32 {
|
|
||||||
self.probability_percents.len() as u32 - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn post_configure(&mut self, tag: &String) {
|
|
||||||
self.points.sort_by_key(|point| point.start_pity);
|
|
||||||
|
|
||||||
let mut probability_percents: Vec<f64> = vec![0.0];
|
|
||||||
for (i, point) in self.points.iter().enumerate() {
|
|
||||||
if i > 0 {
|
|
||||||
let last_point = &self.points[i - 1];
|
|
||||||
let last_stop_percent = last_point.start_chance_percent
|
|
||||||
+ last_point.increment_percent
|
|
||||||
* (point.start_pity - last_point.start_pity) as f64;
|
|
||||||
if last_stop_percent > point.start_chance_percent {
|
|
||||||
tracing::warn!("Gacha - ProbabilityModel '{tag}': The start chance of '{point:?}' is less than the value inherited from the previous point.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut max_pity = 2000;
|
|
||||||
if i < self.points.len() - 1 {
|
|
||||||
let next_point = &self.points[i + 1];
|
|
||||||
max_pity = next_point.start_pity - 1;
|
|
||||||
let max_probability = point.start_chance_percent
|
|
||||||
+ point.increment_percent
|
|
||||||
* (next_point.start_pity - 1 - point.start_pity) as f64;
|
|
||||||
assert!(max_probability < 100.0, "Gacha - ProbabilityModel '{tag}': Probability already reached 100% in '{point:?}' (though points with higher pity left)");
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut pity = point.start_pity;
|
|
||||||
let mut percent = point.start_chance_percent;
|
|
||||||
while pity <= max_pity {
|
|
||||||
if max_pity >= 2000 && percent >= 100.0 {
|
|
||||||
probability_percents.push(100.0);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
probability_percents.push(percent);
|
|
||||||
percent += point.increment_percent;
|
|
||||||
pity += 1;
|
|
||||||
}
|
|
||||||
assert!(pity <= 2000, "Gacha - ProbabilityModel '{tag}' (point {i}): Haven't reached 100% guarantee probability at Pity 2001. The current probability is {percent}%. Crazy.");
|
|
||||||
}
|
|
||||||
|
|
||||||
self.probability_percents = probability_percents;
|
|
||||||
if self.maximum_guarantee_pity <= 0 {
|
|
||||||
self.maximum_guarantee_pity = self.get_maximum_guarantee();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_chance_percent(&self, pity: &u32) -> f64 {
|
|
||||||
// The vec length is 1 bigger than the maximum pity (1-based)
|
|
||||||
let guarantee_pity = self.probability_percents.len() - 1;
|
|
||||||
let idx = *pity as usize;
|
|
||||||
if idx > guarantee_pity {
|
|
||||||
return self.probability_percents[guarantee_pity];
|
|
||||||
}
|
|
||||||
self.probability_percents[idx]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Debug, Default, Deserialize)]
|
|
||||||
pub struct CategoryGuaranteePolicy {
|
|
||||||
pub included_category_tags: HashSet<String>,
|
|
||||||
pub trigger_on_failure_times: u32,
|
|
||||||
pub clear_status_on_target_changed: bool,
|
|
||||||
pub chooseable: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize)]
|
|
||||||
pub struct TenPullDiscount {
|
|
||||||
pub use_limit: u32,
|
|
||||||
pub discounted_prize: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize)]
|
|
||||||
pub struct AdvancedGuarantee {
|
|
||||||
pub use_limit: u32,
|
|
||||||
pub rarity: u32,
|
|
||||||
pub guarantee_pity: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize)]
|
|
||||||
pub struct MustGainItem {
|
|
||||||
pub use_limit: u32,
|
|
||||||
pub rarity: u32,
|
|
||||||
pub category_tag: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize)]
|
|
||||||
pub struct DiscountPolicyCollection {
|
|
||||||
pub ten_pull_discount_map: HashMap<String, TenPullDiscount>,
|
|
||||||
pub must_gain_item_map: HashMap<String, MustGainItem>,
|
|
||||||
pub advanced_guarantee_map: HashMap<String, AdvancedGuarantee>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DiscountPolicyCollection {
|
|
||||||
pub fn post_configure(&mut self) {
|
|
||||||
for (tag, ten_pull_discount) in self.ten_pull_discount_map.iter() {
|
|
||||||
let discounted_prize = ten_pull_discount.discounted_prize;
|
|
||||||
assert!(discounted_prize < 10, "Gacha - DiscountPolicy '{tag}': ten_pull_discount's value should be smaller than 10 (read {discounted_prize}).");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize)]
|
|
||||||
pub struct GachaCategoryInfo {
|
|
||||||
#[serde(default)]
|
|
||||||
pub is_promotional_items: bool,
|
|
||||||
pub item_ids: Vec<u32>,
|
|
||||||
pub category_weight: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize)]
|
|
||||||
pub struct GachaAvailableItemsInfo {
|
|
||||||
pub rarity: u32,
|
|
||||||
#[serde(default)]
|
|
||||||
pub extra_items_policy_tags: Vec<String>,
|
|
||||||
pub categories: HashMap<String, GachaCategoryInfo>,
|
|
||||||
pub probability_model_tag: String,
|
|
||||||
#[serde(default)]
|
|
||||||
pub category_guarantee_policy_tags: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Debug, Default, Deserialize)]
|
|
||||||
pub struct CharacterGachaPool {
|
|
||||||
pub gacha_schedule_id: u32,
|
|
||||||
pub gacha_parent_schedule_id: u32,
|
|
||||||
pub comment: String,
|
|
||||||
pub gacha_type: u32,
|
|
||||||
pub cost_item_id: u32,
|
|
||||||
pub start_time: DateTime<Local>,
|
|
||||||
pub end_time: DateTime<Local>,
|
|
||||||
#[serde(default)]
|
|
||||||
pub discount_policy_tags: Vec<String>,
|
|
||||||
pub sharing_guarantee_info_category: String,
|
|
||||||
pub gacha_items: Vec<GachaAvailableItemsInfo>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CharacterGachaPool {
|
|
||||||
pub fn is_still_open(&self, now: &DateTime<Local>) -> bool {
|
|
||||||
self.start_time <= *now && *now <= self.end_time
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn post_configure(&mut self, probability_model_map: &HashMap<String, ProbabilityModel>) {
|
|
||||||
self.gacha_items
|
|
||||||
.sort_by_key(|item_list| u32::MAX - item_list.rarity);
|
|
||||||
for items_info in self.gacha_items.iter_mut() {
|
|
||||||
assert!(probability_model_map.contains_key(&items_info.probability_model_tag), "Gacha - CharacterGachaPool '{}': Specified ProbabilityModel tag '{}' that does not exist.", self.gacha_schedule_id, items_info.probability_model_tag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize)]
|
|
||||||
pub struct GachaCommonProperties {
|
|
||||||
pub up_item_category_tag: String,
|
|
||||||
pub s_item_rarity: u32,
|
|
||||||
pub a_item_rarity: u32,
|
|
||||||
// TODO: PostConfigure check
|
|
||||||
pub ten_pull_discount_tag: String,
|
|
||||||
pub newcomer_advanced_s_tag: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize)]
|
|
||||||
pub struct GachaConfiguration {
|
|
||||||
pub character_gacha_pool_list: Vec<CharacterGachaPool>,
|
|
||||||
pub probability_model_map: HashMap<String, ProbabilityModel>,
|
|
||||||
pub category_guarantee_policy_map: HashMap<String, CategoryGuaranteePolicy>,
|
|
||||||
pub extra_items_policy_map: HashMap<String, ExtraItemsPolicy>,
|
|
||||||
pub discount_policies: DiscountPolicyCollection,
|
|
||||||
pub common_properties: GachaCommonProperties,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GachaConfiguration {
|
|
||||||
pub fn post_configure(&mut self) {
|
|
||||||
assert!(
|
|
||||||
self.category_guarantee_policy_map
|
|
||||||
.contains_key(&self.common_properties.up_item_category_tag),
|
|
||||||
"The UP category should be valid in policy map."
|
|
||||||
);
|
|
||||||
|
|
||||||
for (tag, policy) in self.probability_model_map.iter_mut() {
|
|
||||||
policy.post_configure(&tag);
|
|
||||||
}
|
|
||||||
self.discount_policies.post_configure();
|
|
||||||
for character_pool in self.character_gacha_pool_list.iter_mut() {
|
|
||||||
character_pool.post_configure(&self.probability_model_map);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
use std::sync::OnceLock;
|
|
||||||
|
|
||||||
use gacha_config::GachaConfiguration;
|
|
||||||
use jsonc_parser::{parse_to_serde_value, ParseOptions};
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
use crate::{action::ActionConfig, DataLoadError};
|
|
||||||
|
|
||||||
pub mod gacha_config;
|
|
||||||
|
|
||||||
const GACHA_CONFIG_NAME: &str = "gacha.jsonc";
|
|
||||||
static GACHACONF: OnceLock<GachaConfiguration> = OnceLock::new();
|
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
|
||||||
pub struct EventGraphConfig {
|
|
||||||
pub event_id: u32,
|
|
||||||
pub actions: Vec<ActionConfig>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn load_gacha_config(path: &str) -> Result<(), DataLoadError> {
|
|
||||||
let jsonc_data = std::fs::read_to_string(format!("{path}/{GACHA_CONFIG_NAME}"))
|
|
||||||
.map_err(|err| DataLoadError::IoError(err))?;
|
|
||||||
|
|
||||||
let json_value = parse_to_serde_value(
|
|
||||||
&jsonc_data,
|
|
||||||
&ParseOptions {
|
|
||||||
allow_comments: true,
|
|
||||||
allow_loose_object_property_names: false,
|
|
||||||
allow_trailing_commas: true,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.map_err(|err| DataLoadError::JsoncParseError(err))?
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mut result = serde_json::from_value::<GachaConfiguration>(json_value)
|
|
||||||
.map_err(|err| DataLoadError::FromJsonError(String::from("GachaConfiguration"), err))?;
|
|
||||||
result.post_configure();
|
|
||||||
|
|
||||||
GACHACONF.set(result).unwrap();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn global_gacha_config() -> &'static GachaConfiguration {
|
|
||||||
GACHACONF.get().unwrap()
|
|
||||||
}
|
|
|
@ -1,11 +1,9 @@
|
||||||
pub mod action;
|
pub mod action;
|
||||||
pub mod event;
|
pub mod event;
|
||||||
pub mod gacha;
|
|
||||||
pub mod tables;
|
pub mod tables;
|
||||||
|
|
||||||
use std::{collections::HashMap, sync::OnceLock};
|
use std::{collections::HashMap, sync::OnceLock};
|
||||||
|
|
||||||
use jsonc_parser::errors::ParseError;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
@ -14,7 +12,6 @@ pub struct AssetsConfig {
|
||||||
pub filecfg_path: String,
|
pub filecfg_path: String,
|
||||||
pub event_config_path: String,
|
pub event_config_path: String,
|
||||||
pub usm_keys_path: String,
|
pub usm_keys_path: String,
|
||||||
pub gacha_config_path: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
|
@ -23,8 +20,6 @@ pub enum DataLoadError {
|
||||||
IoError(#[from] std::io::Error),
|
IoError(#[from] std::io::Error),
|
||||||
#[error("from_json failed for type {0}, error: {1}")]
|
#[error("from_json failed for type {0}, error: {1}")]
|
||||||
FromJsonError(String, serde_json::Error),
|
FromJsonError(String, serde_json::Error),
|
||||||
#[error("jsonc_parser parse as json error: {0}")]
|
|
||||||
JsoncParseError(#[from] ParseError),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static USM_KEY_MAP: OnceLock<HashMap<u32, u64>> = OnceLock::new();
|
static USM_KEY_MAP: OnceLock<HashMap<u32, u64>> = OnceLock::new();
|
||||||
|
@ -36,7 +31,6 @@ pub fn init_data(config: &AssetsConfig) -> Result<(), DataLoadError> {
|
||||||
tracing::warn!("failed to load USM keys, in-game cutscenes will not work! Reason: {err}");
|
tracing::warn!("failed to load USM keys, in-game cutscenes will not work! Reason: {err}");
|
||||||
USM_KEY_MAP.set(HashMap::new()).unwrap();
|
USM_KEY_MAP.set(HashMap::new()).unwrap();
|
||||||
}
|
}
|
||||||
gacha::load_gacha_config(&config.gacha_config_path)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,4 +41,3 @@ tracing-bunyan-formatter.workspace = true
|
||||||
common.workspace = true
|
common.workspace = true
|
||||||
data.workspace = true
|
data.workspace = true
|
||||||
proto.workspace = true
|
proto.workspace = true
|
||||||
chrono = { version = "0.4.38", features = ["serde"] }
|
|
||||||
|
|
|
@ -21,7 +21,6 @@ impl Default for NapGSConfig {
|
||||||
filecfg_path: String::from("assets/FileCfg"),
|
filecfg_path: String::from("assets/FileCfg"),
|
||||||
event_config_path: String::from("assets/EventConfig"),
|
event_config_path: String::from("assets/EventConfig"),
|
||||||
usm_keys_path: String::from("assets/VideoUSMEncKeys.json"),
|
usm_keys_path: String::from("assets/VideoUSMEncKeys.json"),
|
||||||
gacha_config_path: String::from("assets/GachaConfig"),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,234 +1,13 @@
|
||||||
use data::gacha::{gacha_config::*, global_gacha_config};
|
|
||||||
|
|
||||||
use proto::*;
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use chrono::{DateTime, Local};
|
|
||||||
|
|
||||||
pub async fn on_get_gacha_data(
|
pub async fn on_get_gacha_data(
|
||||||
_session: &NetSession,
|
_session: &NetSession,
|
||||||
_player: &mut Player,
|
_player: &mut Player,
|
||||||
req: GetGachaDataCsReq,
|
req: GetGachaDataCsReq,
|
||||||
) -> NetResult<GetGachaDataScRsp> {
|
) -> NetResult<GetGachaDataScRsp> {
|
||||||
if req.gacha_type != 3 {
|
Ok(GetGachaDataScRsp {
|
||||||
// tracing::info!("non-supported gacha type {}", body.gacha_type);
|
|
||||||
Ok(GetGachaDataScRsp {
|
|
||||||
retcode: Retcode::RetSucc.into(),
|
|
||||||
gacha_type: req.gacha_type,
|
|
||||||
gacha_data: Some(GachaData::default()),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// tracing::info!("construct gacha info");
|
|
||||||
Ok(GetGachaDataScRsp {
|
|
||||||
retcode: Retcode::RetSucc.into(),
|
|
||||||
gacha_type: req.gacha_type,
|
|
||||||
gacha_data: Some(generate_all_gacha_info(_player)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn on_do_gacha(
|
|
||||||
_session: &NetSession,
|
|
||||||
_player: &mut Player,
|
|
||||||
req: DoGachaCsReq,
|
|
||||||
) -> NetResult<DoGachaScRsp> {
|
|
||||||
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(DoGachaScRsp {
|
|
||||||
retcode: Retcode::RetSucc.into(),
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
};
|
|
||||||
let target_pool = target_pool.unwrap();
|
|
||||||
|
|
||||||
// TODO: Validate cost_item_count
|
|
||||||
// tracing::info!("cost_item_count: {}", body.cost_item_count);
|
|
||||||
let mut pull_count = if req.cost_item_count > 1 { 10 } else { 1 };
|
|
||||||
|
|
||||||
if pull_count == 10 {
|
|
||||||
let discount_tag = &gachaconf.common_properties.ten_pull_discount_tag;
|
|
||||||
if target_pool.discount_policy_tags.contains(&discount_tag) {
|
|
||||||
let gacha_bin = &mut gacha_model.gacha_bin;
|
|
||||||
let status_bin = gacha_bin
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut gain_item_list: Vec<GainItemInfo> = vec![];
|
|
||||||
while pull_count > 0 {
|
|
||||||
let pull_result = gacha_model.perform_pull_pool(&pull_time, target_pool);
|
|
||||||
let extra_item_bin = pull_result.extra_item_bin.unwrap();
|
|
||||||
gain_item_list.push(GainItemInfo {
|
|
||||||
item_id: pull_result.obtained_item_id,
|
|
||||||
extra_item_id: extra_item_bin.extra_item_id,
|
|
||||||
extra_item_count: extra_item_bin.extra_item_count,
|
|
||||||
num: 1,
|
|
||||||
..GainItemInfo::default()
|
|
||||||
});
|
|
||||||
pull_count -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(DoGachaScRsp {
|
|
||||||
retcode: Retcode::RetSucc.into(),
|
retcode: Retcode::RetSucc.into(),
|
||||||
gain_item_list,
|
gacha_type: req.gacha_type,
|
||||||
gacha_data: Some(generate_all_gacha_info(_player)),
|
gacha_data: Some(GachaData::default()),
|
||||||
cost_item_count: req.cost_item_count,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_gacha_info_from_pool(
|
|
||||||
gacha_bin: &GachaModelBin,
|
|
||||||
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 = gacha_bin
|
|
||||||
.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 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_avaliable_num: u32 = 0;
|
|
||||||
let mut advanced_s_guarantee: u32 = 0;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let need_item_info_list: Vec<NeedItemInfo> = vec![NeedItemInfo {
|
|
||||||
need_item_id: target_pool.cost_item_id,
|
|
||||||
need_item_count: 1,
|
|
||||||
}];
|
|
||||||
|
|
||||||
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,
|
|
||||||
up_s_item_list,
|
|
||||||
up_a_item_list,
|
|
||||||
advanced_s_guarantee,
|
|
||||||
s_guarantee,
|
|
||||||
a_guarantee,
|
|
||||||
need_item_info_list,
|
|
||||||
// iehkehofjop: target_pool.gacha_parent_schedule_id,
|
|
||||||
// eggcehlgkii: 223,
|
|
||||||
// ijoahiepmfo: 101,
|
|
||||||
..Gacha::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn generate_all_gacha_info(_player: &Player) -> GachaData {
|
|
||||||
let gachaconf = global_gacha_config();
|
|
||||||
let gacha_bin = &_player.gacha_model.gacha_bin;
|
|
||||||
let mut gacha_list: Vec<Gacha> = vec![];
|
|
||||||
for target_pool in gachaconf.character_gacha_pool_list.iter() {
|
|
||||||
gacha_list.push(generate_gacha_info_from_pool(
|
|
||||||
&gacha_bin,
|
|
||||||
target_pool,
|
|
||||||
&gachaconf.common_properties,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
GachaData {
|
|
||||||
random_number: 0,
|
|
||||||
gacha_pool: Some(GachaPool { gacha_list }),
|
|
||||||
..GachaData::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_gacha_pool<'conf>(
|
|
||||||
character_gacha_pool_list: &'conf Vec<CharacterGachaPool>,
|
|
||||||
gacha_parent_schedule_id: &u32,
|
|
||||||
pull_time: &DateTime<Local>,
|
|
||||||
) -> Option<&'conf CharacterGachaPool> {
|
|
||||||
for target_pool in character_gacha_pool_list.iter() {
|
|
||||||
if &target_pool.gacha_parent_schedule_id == gacha_parent_schedule_id
|
|
||||||
&& target_pool.is_still_open(pull_time)
|
|
||||||
{
|
|
||||||
return Some(target_pool);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
|
@ -119,7 +119,6 @@ req_handlers! {
|
||||||
event_graph::RunEventGraph;
|
event_graph::RunEventGraph;
|
||||||
quest::BeginArchiveBattleQuest;
|
quest::BeginArchiveBattleQuest;
|
||||||
quest::FinishArchiveQuest;
|
quest::FinishArchiveQuest;
|
||||||
gacha::DoGacha;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
notify_handlers! {
|
notify_handlers! {
|
||||||
|
|
|
@ -1,390 +0,0 @@
|
||||||
use data::gacha;
|
|
||||||
use data::gacha::gacha_config::*;
|
|
||||||
use proto::*;
|
|
||||||
|
|
||||||
use chrono::{DateTime, Local};
|
|
||||||
use rand::{thread_rng, Rng};
|
|
||||||
use std::collections::{HashMap, HashSet};
|
|
||||||
use std::hash::{BuildHasher, Hash};
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct GachaModel {
|
|
||||||
pub gacha_bin: GachaModelBin,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GachaModel {
|
|
||||||
pub fn from_bin(gacha_bin: GachaModelBin) -> Self {
|
|
||||||
let result = Self { gacha_bin };
|
|
||||||
result.post_deserialize()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_bin(&self) -> GachaModelBin {
|
|
||||||
self.gacha_bin.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn post_deserialize(mut self) -> GachaModel {
|
|
||||||
let gachaconf = gacha::global_gacha_config();
|
|
||||||
for gacha_pool in gachaconf.character_gacha_pool_list.iter() {
|
|
||||||
let gacha_comp_bin = &mut self.gacha_bin;
|
|
||||||
let mut gacha_status_map = &mut gacha_comp_bin.gacha_status_map;
|
|
||||||
let status_bin = get_or_add(
|
|
||||||
&mut gacha_status_map,
|
|
||||||
&gacha_pool.sharing_guarantee_info_category,
|
|
||||||
);
|
|
||||||
for rarity_items in gacha_pool.gacha_items.iter() {
|
|
||||||
let progress_bin =
|
|
||||||
get_or_add(&mut status_bin.rarity_status_map, &rarity_items.rarity);
|
|
||||||
if progress_bin.pity <= 0 {
|
|
||||||
progress_bin.pity = 1;
|
|
||||||
}
|
|
||||||
for category_guarantee_policy_tag in
|
|
||||||
rarity_items.category_guarantee_policy_tags.iter()
|
|
||||||
{
|
|
||||||
get_or_add(
|
|
||||||
&mut progress_bin.categories_progress_map,
|
|
||||||
&category_guarantee_policy_tag,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for discount_policy_tag in gacha_pool.discount_policy_tags.iter() {
|
|
||||||
get_or_add(&mut status_bin.discount_usage_map, &discount_policy_tag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn perform_pull_pool<'bin, 'conf>(
|
|
||||||
&'bin mut self,
|
|
||||||
pull_time: &DateTime<Local>,
|
|
||||||
target_pool: &'conf CharacterGachaPool,
|
|
||||||
) -> GachaRecordBin {
|
|
||||||
let mut gacha_bin = &mut self.gacha_bin;
|
|
||||||
let (rarity_items, progress_bin, status_bin, probability_model) =
|
|
||||||
determine_rarity(&gacha_bin, target_pool);
|
|
||||||
let (category_tag, category) = determine_category(rarity_items, progress_bin, target_pool);
|
|
||||||
let result =
|
|
||||||
determine_gacha_result(pull_time, category, target_pool, status_bin, rarity_items);
|
|
||||||
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_discount(&mut gacha_bin, target_pool, &category_tag, rarity_items);
|
|
||||||
result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_or_add<'a, K: Eq + PartialEq + Hash + Clone, V: Default, S: BuildHasher>(
|
|
||||||
map: &'a mut HashMap<K, V, S>,
|
|
||||||
key: &K,
|
|
||||||
) -> &'a mut V {
|
|
||||||
if !map.contains_key(key) {
|
|
||||||
map.insert(key.clone(), V::default());
|
|
||||||
}
|
|
||||||
map.get_mut(key).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rand_rarity<'bin, 'conf>(
|
|
||||||
target_pool: &'conf CharacterGachaPool,
|
|
||||||
status_bin: &'bin GachaStatusBin,
|
|
||||||
) -> (
|
|
||||||
&'conf GachaAvailableItemsInfo,
|
|
||||||
&'bin GachaProgressBin,
|
|
||||||
&'conf ProbabilityModel,
|
|
||||||
) {
|
|
||||||
let gachaconf = gacha::global_gacha_config();
|
|
||||||
let mut rng = thread_rng();
|
|
||||||
let rarity_status_map = &status_bin.rarity_status_map;
|
|
||||||
// gacha_items is already sorted by rarity descendingly in its post_configure.
|
|
||||||
for rarity_items in target_pool.gacha_items.iter() {
|
|
||||||
// Surely any judgement should be made on the current pity.
|
|
||||||
let progress_bin = rarity_status_map.get(&rarity_items.rarity).unwrap();
|
|
||||||
let pity = progress_bin.pity;
|
|
||||||
let probability_model = gachaconf
|
|
||||||
.probability_model_map
|
|
||||||
.get(&rarity_items.probability_model_tag)
|
|
||||||
.unwrap();
|
|
||||||
if rng.gen_range(0.0..100.0) <= probability_model.get_chance_percent(&pity) {
|
|
||||||
return (rarity_items, progress_bin, probability_model);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
panic!("The user failed to get any items.");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn determine_rarity<'bin, 'conf>(
|
|
||||||
gacha_bin: &'bin GachaModelBin,
|
|
||||||
target_pool: &'conf CharacterGachaPool,
|
|
||||||
) -> (
|
|
||||||
&'conf GachaAvailableItemsInfo,
|
|
||||||
&'bin GachaProgressBin,
|
|
||||||
&'bin GachaStatusBin,
|
|
||||||
&'conf ProbabilityModel,
|
|
||||||
) {
|
|
||||||
let gachaconf = gacha::global_gacha_config();
|
|
||||||
let status_bin = gacha_bin
|
|
||||||
.gacha_status_map
|
|
||||||
.get(&target_pool.sharing_guarantee_info_category)
|
|
||||||
.expect(&format!(
|
|
||||||
"post_deserialize forgot StatusBin/sharing_guarantee_info_category: {}",
|
|
||||||
target_pool.sharing_guarantee_info_category
|
|
||||||
));
|
|
||||||
let (mut rarity_items, mut progress_bin, mut probability_model) =
|
|
||||||
rand_rarity(target_pool, &status_bin);
|
|
||||||
|
|
||||||
// We should take AdvancedGuarantee discount into consideration.
|
|
||||||
for discount_tag in target_pool.discount_policy_tags.iter() {
|
|
||||||
if let Some(discount) = gachaconf
|
|
||||||
.discount_policies
|
|
||||||
.advanced_guarantee_map
|
|
||||||
.get(discount_tag)
|
|
||||||
{
|
|
||||||
if discount.rarity <= rarity_items.rarity {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if status_bin
|
|
||||||
.discount_usage_map
|
|
||||||
.get(discount_tag)
|
|
||||||
.expect(&format!(
|
|
||||||
"post_deserialize forgot StatusBin/discount_usage_map: {}",
|
|
||||||
discount_tag
|
|
||||||
))
|
|
||||||
>= &discount.use_limit
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let higher_progress_bin =
|
|
||||||
status_bin
|
|
||||||
.rarity_status_map
|
|
||||||
.get(&discount.rarity)
|
|
||||||
.expect(&format!(
|
|
||||||
"post_deserialize forgot StatusBin/rarity_status_map: {}",
|
|
||||||
&discount.rarity
|
|
||||||
));
|
|
||||||
if higher_progress_bin.pity >= discount.guarantee_pity {
|
|
||||||
let mut found_rarity_items = false;
|
|
||||||
for gacha_items in target_pool.gacha_items.iter() {
|
|
||||||
if gacha_items.rarity == discount.rarity {
|
|
||||||
rarity_items = gacha_items;
|
|
||||||
probability_model = gachaconf
|
|
||||||
.probability_model_map
|
|
||||||
.get(&gacha_items.probability_model_tag)
|
|
||||||
.unwrap();
|
|
||||||
found_rarity_items = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert!(found_rarity_items, "Handle AdvancedGuarantee Discount ({discount_tag}) error: The target rarity does not exist in this pool.");
|
|
||||||
progress_bin = higher_progress_bin;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(rarity_items, progress_bin, status_bin, probability_model)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn determine_category<'bin, 'conf>(
|
|
||||||
rarity_items: &'conf GachaAvailableItemsInfo,
|
|
||||||
progress_bin: &'bin GachaProgressBin,
|
|
||||||
target_pool: &'conf CharacterGachaPool,
|
|
||||||
) -> (String, &'conf GachaCategoryInfo) {
|
|
||||||
let gachaconf = gacha::global_gacha_config();
|
|
||||||
let mut category_tag_inited = false;
|
|
||||||
let mut category_tag_result: HashSet<String> = HashSet::new();
|
|
||||||
// First of all, if there's a chooseable category and
|
|
||||||
// it's can be triggered, then we MUST give that
|
|
||||||
// category's item.
|
|
||||||
// TODO: Only Genshin can do
|
|
||||||
// Then we should take a look at MustGainItem.
|
|
||||||
if !category_tag_inited {
|
|
||||||
for discount_policy_tag in target_pool.discount_policy_tags.iter() {
|
|
||||||
if let Some(discount) = gachaconf
|
|
||||||
.discount_policies
|
|
||||||
.must_gain_item_map
|
|
||||||
.get(discount_policy_tag)
|
|
||||||
{
|
|
||||||
if discount.rarity != rarity_items.rarity {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
category_tag_result.insert(discount.category_tag.clone());
|
|
||||||
category_tag_inited = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Otherwise, just select as normal.
|
|
||||||
if !category_tag_inited {
|
|
||||||
for tag in rarity_items.categories.keys() {
|
|
||||||
category_tag_result.insert(tag.clone());
|
|
||||||
}
|
|
||||||
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();
|
|
||||||
let failure_times = progress_bin.categories_progress_map
|
|
||||||
.get(guarantee_policy_tag)
|
|
||||||
.expect(&format!("post_deserialize forgot StatusBin/rarity_status_map[{}]/categories_progress_map: {}", &rarity_items.rarity, guarantee_policy_tag));
|
|
||||||
if failure_times >= &category_guarantee_policy.trigger_on_failure_times {
|
|
||||||
category_tag_result = category_tag_result
|
|
||||||
.intersection(&category_guarantee_policy.included_category_tags)
|
|
||||||
.cloned()
|
|
||||||
.collect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// category_tag_inited = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut categories: Vec<(String, &GachaCategoryInfo)> = vec![];
|
|
||||||
let mut weight_sum = 0;
|
|
||||||
for result_tag in category_tag_result {
|
|
||||||
let category = rarity_items.categories.get(&result_tag).unwrap();
|
|
||||||
categories.push((result_tag, category));
|
|
||||||
weight_sum += category.category_weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
let randomnum = rand::thread_rng().gen_range(0..weight_sum);
|
|
||||||
let mut enumerated_ranges_end = 0;
|
|
||||||
for category in categories.into_iter() {
|
|
||||||
if randomnum <= enumerated_ranges_end + category.1.category_weight {
|
|
||||||
return (category.0, category.1);
|
|
||||||
}
|
|
||||||
enumerated_ranges_end += category.1.category_weight;
|
|
||||||
}
|
|
||||||
panic!("No category is chosen.");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn determine_gacha_result<'bin, 'conf>(
|
|
||||||
pull_time: &DateTime<Local>,
|
|
||||||
category: &'conf GachaCategoryInfo,
|
|
||||||
target_pool: &'conf CharacterGachaPool,
|
|
||||||
status_bin: &'bin GachaStatusBin,
|
|
||||||
rarity_items: &'conf GachaAvailableItemsInfo,
|
|
||||||
) -> GachaRecordBin {
|
|
||||||
let gachaconf = gacha::global_gacha_config();
|
|
||||||
let item_pool_len = category.item_ids.len() as u32;
|
|
||||||
let item_id = 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_count: u32 = 0;
|
|
||||||
|
|
||||||
for extra_items_policy_tag in rarity_items.extra_items_policy_tags.iter() {
|
|
||||||
let extra_items_policy = gachaconf
|
|
||||||
.extra_items_policy_map
|
|
||||||
.get(extra_items_policy_tag)
|
|
||||||
.unwrap();
|
|
||||||
// TODO: apply_on_owned_count in a context with bag
|
|
||||||
if extra_items_policy.apply_on_owned_count == 0 {
|
|
||||||
extra_item_id = extra_items_policy.id;
|
|
||||||
extra_item_count = extra_items_policy.count;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let extra_item_bin = GachaExtraItemBin {
|
|
||||||
extra_item_id,
|
|
||||||
extra_item_count,
|
|
||||||
currently_gained: 0,
|
|
||||||
};
|
|
||||||
GachaRecordBin {
|
|
||||||
pull_timestamp: pull_time.timestamp(),
|
|
||||||
obtained_item_id: item_id.clone(),
|
|
||||||
gacha_id: target_pool.gacha_schedule_id.clone(),
|
|
||||||
progress_map: status_bin.rarity_status_map.clone(),
|
|
||||||
extra_item_bin: Some(extra_item_bin),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_pity<'bin, 'conf>(
|
|
||||||
gacha_bin: &'bin mut GachaModelBin,
|
|
||||||
rarity_items: &'conf GachaAvailableItemsInfo,
|
|
||||||
probability_model: &'conf ProbabilityModel,
|
|
||||||
target_pool: &'conf CharacterGachaPool,
|
|
||||||
) {
|
|
||||||
let status_bin = gacha_bin
|
|
||||||
.gacha_status_map
|
|
||||||
.get_mut(&target_pool.sharing_guarantee_info_category)
|
|
||||||
.unwrap();
|
|
||||||
for (rarity, rarity_status) in status_bin.rarity_status_map.iter_mut() {
|
|
||||||
if (rarity == &rarity_items.rarity)
|
|
||||||
|| (probability_model.clear_status_on_higher_rarity_pulled
|
|
||||||
&& rarity < &rarity_items.rarity)
|
|
||||||
{
|
|
||||||
rarity_status.pity = 1;
|
|
||||||
} else {
|
|
||||||
rarity_status.pity += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_category_guarantee_info<'bin, 'conf>(
|
|
||||||
gacha_bin: &'bin mut GachaModelBin,
|
|
||||||
rarity_items: &'conf GachaAvailableItemsInfo,
|
|
||||||
category_tag: &String,
|
|
||||||
target_pool: &'conf CharacterGachaPool,
|
|
||||||
) {
|
|
||||||
let gachaconf = gacha::global_gacha_config();
|
|
||||||
let status_bin = gacha_bin
|
|
||||||
.gacha_status_map
|
|
||||||
.get_mut(&target_pool.sharing_guarantee_info_category)
|
|
||||||
.unwrap();
|
|
||||||
let progress_bin = status_bin
|
|
||||||
.rarity_status_map
|
|
||||||
.get_mut(&rarity_items.rarity)
|
|
||||||
.unwrap();
|
|
||||||
for policy_tag in rarity_items.category_guarantee_policy_tags.iter() {
|
|
||||||
let policy = gachaconf
|
|
||||||
.category_guarantee_policy_map
|
|
||||||
.get(policy_tag)
|
|
||||||
.unwrap();
|
|
||||||
// TODO: Chooseable guarantee not implemented
|
|
||||||
let prev_failure = progress_bin
|
|
||||||
.categories_progress_map
|
|
||||||
.get_mut(policy_tag)
|
|
||||||
.expect(&format!(
|
|
||||||
"post_deserialize forgot StatusBin/rarity_status_map[{}]/categories_progress_map: {}",
|
|
||||||
rarity_items.rarity, policy_tag
|
|
||||||
));
|
|
||||||
if policy.included_category_tags.contains(category_tag) {
|
|
||||||
*prev_failure = 0;
|
|
||||||
} else {
|
|
||||||
*prev_failure += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_discount<'bin, 'conf>(
|
|
||||||
gacha_bin: &'bin mut GachaModelBin,
|
|
||||||
target_pool: &'conf CharacterGachaPool,
|
|
||||||
category_tag: &String,
|
|
||||||
rarity_items: &GachaAvailableItemsInfo,
|
|
||||||
) {
|
|
||||||
let gachaconf = gacha::global_gacha_config();
|
|
||||||
for (policy_tag, policy) in gachaconf.discount_policies.must_gain_item_map.iter() {
|
|
||||||
if *category_tag != policy.category_tag {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
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 usage = status_bin.discount_usage_map.get_mut(policy_tag).unwrap();
|
|
||||||
if *usage < policy.use_limit {
|
|
||||||
*usage += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (policy_tag, policy) in gachaconf.discount_policies.advanced_guarantee_map.iter() {
|
|
||||||
if rarity_items.rarity != policy.rarity {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
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 usage = status_bin.discount_usage_map.get_mut(policy_tag).unwrap();
|
|
||||||
if *usage < policy.use_limit {
|
|
||||||
*usage += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
mod gacha_model;
|
|
||||||
mod record;
|
|
||||||
|
|
||||||
pub use gacha_model::GachaModel;
|
|
|
@ -1,6 +1,5 @@
|
||||||
pub mod battle;
|
pub mod battle;
|
||||||
mod enums;
|
mod enums;
|
||||||
pub mod gacha;
|
|
||||||
pub mod game;
|
pub mod game;
|
||||||
pub mod item;
|
pub mod item;
|
||||||
pub mod math;
|
pub mod math;
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
use data::tables::{self, AvatarBaseID};
|
use data::tables::{self, AvatarBaseID};
|
||||||
use proto::{ItemStatic, PlayerDataBin, Retcode};
|
use proto::{ItemStatic, PlayerDataBin, Retcode};
|
||||||
|
|
||||||
use super::gacha::GachaModel;
|
|
||||||
use super::game::{FrontendGame, FrontendGameError, GameInstance, LogicError};
|
use super::game::{FrontendGame, FrontendGameError, GameInstance, LogicError};
|
||||||
use super::item::{ItemModel, ItemUID};
|
use super::item::{ItemModel, ItemUID};
|
||||||
use super::main_city_model::MainCityModel;
|
use super::main_city_model::MainCityModel;
|
||||||
|
@ -17,7 +16,6 @@ pub struct Player {
|
||||||
pub role_model: RoleModel,
|
pub role_model: RoleModel,
|
||||||
pub item_model: ItemModel,
|
pub item_model: ItemModel,
|
||||||
pub main_city_model: MainCityModel,
|
pub main_city_model: MainCityModel,
|
||||||
pub gacha_model: GachaModel,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Player {
|
impl Player {
|
||||||
|
@ -30,7 +28,6 @@ impl Player {
|
||||||
role_model: Some(self.role_model.to_bin()),
|
role_model: Some(self.role_model.to_bin()),
|
||||||
item_model: Some(self.item_model.to_bin()),
|
item_model: Some(self.item_model.to_bin()),
|
||||||
main_city_model: Some(self.main_city_model.to_bin()),
|
main_city_model: Some(self.main_city_model.to_bin()),
|
||||||
gacha_model: Some(self.gacha_model.to_bin()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +44,6 @@ impl Player {
|
||||||
.main_city_model
|
.main_city_model
|
||||||
.map(MainCityModel::from_bin)
|
.map(MainCityModel::from_bin)
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
gacha_model: bin.gacha_model.map(GachaModel::from_bin).unwrap_or_default(),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -227,7 +227,7 @@ pub struct Nkhnlakggmj {
|
||||||
pub iddehlcpjjn: ::prost::alloc::string::String,
|
pub iddehlcpjjn: ::prost::alloc::string::String,
|
||||||
#[xor(10163)]
|
#[xor(10163)]
|
||||||
#[prost(int64, tag = "14")]
|
#[prost(int64, tag = "14")]
|
||||||
pub end_timestamp: i64,
|
pub phkcdmjheen: i64,
|
||||||
#[xor(8852)]
|
#[xor(8852)]
|
||||||
#[prost(uint32, tag = "8")]
|
#[prost(uint32, tag = "8")]
|
||||||
pub ppnbkmndpjc: u32,
|
pub ppnbkmndpjc: u32,
|
||||||
|
@ -829,7 +829,7 @@ pub struct Fkkojjgepnb {
|
||||||
pub lchdjcdjiik: u32,
|
pub lchdjcdjiik: u32,
|
||||||
#[xor(4851)]
|
#[xor(4851)]
|
||||||
#[prost(int64, tag = "3")]
|
#[prost(int64, tag = "3")]
|
||||||
pub end_timestamp: i64,
|
pub phkcdmjheen: i64,
|
||||||
#[xor(8589)]
|
#[xor(8589)]
|
||||||
#[prost(int32, tag = "505")]
|
#[prost(int32, tag = "505")]
|
||||||
pub lecpejadije: i32,
|
pub lecpejadije: i32,
|
||||||
|
@ -4512,7 +4512,7 @@ pub struct Kancdiinhgh {
|
||||||
#[prost(uint64, tag = "2")]
|
#[prost(uint64, tag = "2")]
|
||||||
pub dbkpbkpcoog: u64,
|
pub dbkpbkpcoog: u64,
|
||||||
#[prost(uint64, tag = "3")]
|
#[prost(uint64, tag = "3")]
|
||||||
pub end_timestamp: u64,
|
pub phkcdmjheen: u64,
|
||||||
}
|
}
|
||||||
#[derive(proto_gen::CmdID)]
|
#[derive(proto_gen::CmdID)]
|
||||||
#[cmdid(2438)]
|
#[cmdid(2438)]
|
||||||
|
@ -8223,7 +8223,7 @@ pub struct Ihhaahinlik {
|
||||||
#[prost(int64, tag = "3")]
|
#[prost(int64, tag = "3")]
|
||||||
pub dbkpbkpcoog: i64,
|
pub dbkpbkpcoog: i64,
|
||||||
#[prost(int64, tag = "4")]
|
#[prost(int64, tag = "4")]
|
||||||
pub end_timestamp: i64,
|
pub phkcdmjheen: i64,
|
||||||
}
|
}
|
||||||
#[derive(proto_gen::CmdID)]
|
#[derive(proto_gen::CmdID)]
|
||||||
#[derive(proto_gen::XorFields)]
|
#[derive(proto_gen::XorFields)]
|
||||||
|
@ -9108,7 +9108,7 @@ pub struct Gkfhklbbcpo {
|
||||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
pub struct Enkkecgonaf {
|
pub struct Enkkecgonaf {
|
||||||
#[prost(uint32, tag = "1")]
|
#[prost(uint32, tag = "1")]
|
||||||
pub cost_item_count: u32,
|
pub abalhhfapla: u32,
|
||||||
}
|
}
|
||||||
#[derive(proto_gen::CmdID)]
|
#[derive(proto_gen::CmdID)]
|
||||||
#[derive(proto_gen::XorFields)]
|
#[derive(proto_gen::XorFields)]
|
||||||
|
@ -9603,7 +9603,7 @@ pub struct Gfpehbhpgfb {
|
||||||
pub avatars: ::prost::alloc::vec::Vec<u32>,
|
pub avatars: ::prost::alloc::vec::Vec<u32>,
|
||||||
#[xor(8960)]
|
#[xor(8960)]
|
||||||
#[prost(uint32, tag = "14")]
|
#[prost(uint32, tag = "14")]
|
||||||
pub cost_item_count: u32,
|
pub abalhhfapla: u32,
|
||||||
#[xor(8889)]
|
#[xor(8889)]
|
||||||
#[prost(uint32, tag = "6")]
|
#[prost(uint32, tag = "6")]
|
||||||
pub damhjcgieco: u32,
|
pub damhjcgieco: u32,
|
||||||
|
@ -9668,7 +9668,7 @@ pub struct Cbccakknimc {
|
||||||
pub struct Bfkkjofnjjo {
|
pub struct Bfkkjofnjjo {
|
||||||
#[xor(4795)]
|
#[xor(4795)]
|
||||||
#[prost(uint32, tag = "10")]
|
#[prost(uint32, tag = "10")]
|
||||||
pub cost_item_count: u32,
|
pub abalhhfapla: u32,
|
||||||
#[xor(5066)]
|
#[xor(5066)]
|
||||||
#[prost(uint32, tag = "4")]
|
#[prost(uint32, tag = "4")]
|
||||||
pub cmacbfkaoma: u32,
|
pub cmacbfkaoma: u32,
|
||||||
|
@ -10605,7 +10605,7 @@ pub struct Npdgpemipij {
|
||||||
#[prost(int32, tag = "5")]
|
#[prost(int32, tag = "5")]
|
||||||
pub peamchapinf: i32,
|
pub peamchapinf: i32,
|
||||||
#[prost(string, tag = "2")]
|
#[prost(string, tag = "2")]
|
||||||
pub end_timestamp: ::prost::alloc::string::String,
|
pub phkcdmjheen: ::prost::alloc::string::String,
|
||||||
}
|
}
|
||||||
#[derive(proto_gen::CmdID)]
|
#[derive(proto_gen::CmdID)]
|
||||||
#[cmdid(5101)]
|
#[cmdid(5101)]
|
||||||
|
@ -12389,7 +12389,7 @@ pub struct InteractWithUnitCsReq {
|
||||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
pub struct Ofcdfiihahe {
|
pub struct Ofcdfiihahe {
|
||||||
#[prost(uint32, tag = "1")]
|
#[prost(uint32, tag = "1")]
|
||||||
pub cost_item_count: u32,
|
pub abalhhfapla: u32,
|
||||||
}
|
}
|
||||||
#[derive(proto_gen::CmdID)]
|
#[derive(proto_gen::CmdID)]
|
||||||
#[derive(proto_gen::XorFields)]
|
#[derive(proto_gen::XorFields)]
|
||||||
|
@ -12926,7 +12926,7 @@ pub struct Hdjijldgmab {
|
||||||
pub struct Cnmgfcllhpl {
|
pub struct Cnmgfcllhpl {
|
||||||
#[xor(11487)]
|
#[xor(11487)]
|
||||||
#[prost(uint32, tag = "3")]
|
#[prost(uint32, tag = "3")]
|
||||||
pub gacha_parent_schedule_id: u32,
|
pub dcealmadfgi: u32,
|
||||||
}
|
}
|
||||||
#[derive(proto_gen::CmdID)]
|
#[derive(proto_gen::CmdID)]
|
||||||
#[cmdid(665)]
|
#[cmdid(665)]
|
||||||
|
@ -13423,7 +13423,7 @@ pub struct Jlldijenmoc {
|
||||||
pub dphghojclod: u32,
|
pub dphghojclod: u32,
|
||||||
#[xor(8783)]
|
#[xor(8783)]
|
||||||
#[prost(int64, tag = "2")]
|
#[prost(int64, tag = "2")]
|
||||||
pub end_timestamp: i64,
|
pub phkcdmjheen: i64,
|
||||||
#[xor(525)]
|
#[xor(525)]
|
||||||
#[prost(uint32, tag = "8")]
|
#[prost(uint32, tag = "8")]
|
||||||
pub leagnodilli: u32,
|
pub leagnodilli: u32,
|
||||||
|
@ -13731,19 +13731,19 @@ pub struct Omdfkjoopce {
|
||||||
#[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 DoGachaCsReq {
|
pub struct Effclengcpo {
|
||||||
#[xor(13734)]
|
#[xor(13734)]
|
||||||
#[prost(uint32, tag = "15")]
|
#[prost(uint32, tag = "15")]
|
||||||
pub eglpecifagd: u32,
|
pub eglpecifagd: u32,
|
||||||
#[xor(4101)]
|
#[xor(4101)]
|
||||||
#[prost(uint32, tag = "1")]
|
#[prost(uint32, tag = "1")]
|
||||||
pub gacha_parent_schedule_id: u32,
|
pub dcealmadfgi: u32,
|
||||||
#[xor(8588)]
|
#[xor(8588)]
|
||||||
#[prost(uint32, tag = "13")]
|
#[prost(uint32, tag = "13")]
|
||||||
pub random_number: u32,
|
pub madciamhahg: u32,
|
||||||
#[xor(10835)]
|
#[xor(10835)]
|
||||||
#[prost(uint32, tag = "3")]
|
#[prost(uint32, tag = "3")]
|
||||||
pub cost_item_count: u32,
|
pub abalhhfapla: u32,
|
||||||
#[xor(3853)]
|
#[xor(3853)]
|
||||||
#[prost(uint32, tag = "5")]
|
#[prost(uint32, tag = "5")]
|
||||||
pub gacha_type: u32,
|
pub gacha_type: u32,
|
||||||
|
@ -14035,10 +14035,10 @@ pub struct Nffblkigaan {
|
||||||
pub weapon_uid: u32,
|
pub weapon_uid: u32,
|
||||||
#[xor(2492)]
|
#[xor(2492)]
|
||||||
#[prost(uint32, tag = "9")]
|
#[prost(uint32, tag = "9")]
|
||||||
pub need_item_id: u32,
|
pub eijhjbplhih: u32,
|
||||||
#[xor(13312)]
|
#[xor(13312)]
|
||||||
#[prost(uint32, tag = "8")]
|
#[prost(uint32, tag = "8")]
|
||||||
pub need_item_count: u32,
|
pub bplmpghdklb: u32,
|
||||||
}
|
}
|
||||||
#[derive(proto_gen::CmdID)]
|
#[derive(proto_gen::CmdID)]
|
||||||
#[derive(proto_gen::XorFields)]
|
#[derive(proto_gen::XorFields)]
|
||||||
|
@ -15705,7 +15705,7 @@ pub struct Gcffmmdgobe {
|
||||||
#[prost(int32, tag = "1")]
|
#[prost(int32, tag = "1")]
|
||||||
pub opmgaofadph: i32,
|
pub opmgaofadph: i32,
|
||||||
#[prost(int32, tag = "2")]
|
#[prost(int32, tag = "2")]
|
||||||
pub cost_item_count: i32,
|
pub abalhhfapla: i32,
|
||||||
}
|
}
|
||||||
#[derive(proto_gen::CmdID)]
|
#[derive(proto_gen::CmdID)]
|
||||||
#[cmdid(1224)]
|
#[cmdid(1224)]
|
||||||
|
@ -16201,13 +16201,13 @@ pub struct ClientSystemsInfo {
|
||||||
#[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 NeedItemInfo {
|
pub struct Feanepokfam {
|
||||||
#[xor(156)]
|
#[xor(156)]
|
||||||
#[prost(uint32, tag = "15")]
|
#[prost(uint32, tag = "15")]
|
||||||
pub need_item_count: u32,
|
pub bplmpghdklb: u32,
|
||||||
#[xor(11997)]
|
#[xor(11997)]
|
||||||
#[prost(uint32, tag = "5")]
|
#[prost(uint32, tag = "5")]
|
||||||
pub need_item_id: u32,
|
pub eijhjbplhih: u32,
|
||||||
}
|
}
|
||||||
#[derive(proto_gen::CmdID)]
|
#[derive(proto_gen::CmdID)]
|
||||||
#[cmdid(1140)]
|
#[cmdid(1140)]
|
||||||
|
@ -16647,14 +16647,14 @@ pub struct Ejjimjcohgg {
|
||||||
#[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 DoGachaScRsp {
|
pub struct Akjiehealco {
|
||||||
#[prost(message, optional, tag = "6")]
|
#[prost(message, optional, tag = "6")]
|
||||||
pub gacha_data: ::core::option::Option<GachaData>,
|
pub gacha_data: ::core::option::Option<GachaData>,
|
||||||
#[prost(message, repeated, tag = "13")]
|
#[prost(message, repeated, tag = "13")]
|
||||||
pub gain_item_list: ::prost::alloc::vec::Vec<GainItemInfo>,
|
pub mjajbddaemm: ::prost::alloc::vec::Vec<Depahhdodeb>,
|
||||||
#[xor(12395)]
|
#[xor(12395)]
|
||||||
#[prost(uint32, tag = "11")]
|
#[prost(uint32, tag = "11")]
|
||||||
pub cost_item_count: u32,
|
pub abalhhfapla: u32,
|
||||||
#[xor(14106)]
|
#[xor(14106)]
|
||||||
#[prost(int32, tag = "5")]
|
#[prost(int32, tag = "5")]
|
||||||
pub retcode: i32,
|
pub retcode: i32,
|
||||||
|
@ -17252,7 +17252,7 @@ pub struct Ipfpofcbnjp {
|
||||||
pub struct Ilehibpgief {
|
pub struct Ilehibpgief {
|
||||||
#[xor(14424)]
|
#[xor(14424)]
|
||||||
#[prost(int64, tag = "4")]
|
#[prost(int64, tag = "4")]
|
||||||
pub end_timestamp: i64,
|
pub phkcdmjheen: i64,
|
||||||
#[prost(uint32, repeated, tag = "6")]
|
#[prost(uint32, repeated, tag = "6")]
|
||||||
pub pjilpbkknan: ::prost::alloc::vec::Vec<u32>,
|
pub pjilpbkknan: ::prost::alloc::vec::Vec<u32>,
|
||||||
#[xor(4341)]
|
#[xor(4341)]
|
||||||
|
@ -17842,7 +17842,7 @@ pub struct Odijcgldmia {
|
||||||
#[prost(uint32, tag = "11")]
|
#[prost(uint32, tag = "11")]
|
||||||
pub bpegheknole: u32,
|
pub bpegheknole: u32,
|
||||||
#[prost(message, repeated, tag = "14")]
|
#[prost(message, repeated, tag = "14")]
|
||||||
pub bapbocgilep: ::prost::alloc::vec::Vec<NeedItemInfo>,
|
pub bapbocgilep: ::prost::alloc::vec::Vec<Feanepokfam>,
|
||||||
#[prost(map = "uint32, uint32", tag = "10")]
|
#[prost(map = "uint32, uint32", tag = "10")]
|
||||||
pub maeegjdknkg: ::std::collections::HashMap<u32, u32>,
|
pub maeegjdknkg: ::std::collections::HashMap<u32, u32>,
|
||||||
#[prost(uint32, repeated, tag = "9")]
|
#[prost(uint32, repeated, tag = "9")]
|
||||||
|
@ -17850,20 +17850,20 @@ pub struct Odijcgldmia {
|
||||||
#[prost(string, tag = "3")]
|
#[prost(string, tag = "3")]
|
||||||
pub fnanndecaan: ::prost::alloc::string::String,
|
pub fnanndecaan: ::prost::alloc::string::String,
|
||||||
#[prost(message, repeated, tag = "13")]
|
#[prost(message, repeated, tag = "13")]
|
||||||
pub need_item_info_list: ::prost::alloc::vec::Vec<NeedItemInfo>,
|
pub pbalmllekpp: ::prost::alloc::vec::Vec<Feanepokfam>,
|
||||||
#[prost(message, repeated, tag = "6")]
|
#[prost(message, repeated, tag = "6")]
|
||||||
pub mplcofohjnl: ::prost::alloc::vec::Vec<NeedItemInfo>,
|
pub mplcofohjnl: ::prost::alloc::vec::Vec<Feanepokfam>,
|
||||||
#[xor(5947)]
|
#[xor(5947)]
|
||||||
#[prost(uint32, tag = "5")]
|
#[prost(uint32, tag = "5")]
|
||||||
pub r#type: u32,
|
pub r#type: u32,
|
||||||
#[prost(message, repeated, tag = "8")]
|
#[prost(message, repeated, tag = "8")]
|
||||||
pub dnadnehoogk: ::prost::alloc::vec::Vec<NeedItemInfo>,
|
pub dnadnehoogk: ::prost::alloc::vec::Vec<Feanepokfam>,
|
||||||
#[xor(5194)]
|
#[xor(5194)]
|
||||||
#[prost(uint32, tag = "1")]
|
#[prost(uint32, tag = "1")]
|
||||||
pub dgaafhidocl: u32,
|
pub dgaafhidocl: u32,
|
||||||
#[xor(11358)]
|
#[xor(11358)]
|
||||||
#[prost(uint32, tag = "12")]
|
#[prost(uint32, tag = "12")]
|
||||||
pub gacha_parent_schedule_id: u32,
|
pub dcealmadfgi: u32,
|
||||||
}
|
}
|
||||||
#[derive(proto_gen::CmdID)]
|
#[derive(proto_gen::CmdID)]
|
||||||
#[derive(proto_gen::XorFields)]
|
#[derive(proto_gen::XorFields)]
|
||||||
|
@ -18737,7 +18737,7 @@ pub struct Ilghjldjhcl {
|
||||||
pub hbmnikpdgon: ::prost::alloc::string::String,
|
pub hbmnikpdgon: ::prost::alloc::string::String,
|
||||||
#[xor(9518)]
|
#[xor(9518)]
|
||||||
#[prost(int64, tag = "9")]
|
#[prost(int64, tag = "9")]
|
||||||
pub end_timestamp: i64,
|
pub phkcdmjheen: i64,
|
||||||
#[xor(1810)]
|
#[xor(1810)]
|
||||||
#[prost(uint32, tag = "1")]
|
#[prost(uint32, tag = "1")]
|
||||||
pub oeaieooemng: u32,
|
pub oeaieooemng: u32,
|
||||||
|
@ -18990,7 +18990,7 @@ pub struct Cobgikcepkp {
|
||||||
#[prost(int32, tag = "1")]
|
#[prost(int32, tag = "1")]
|
||||||
pub imhfejennof: i32,
|
pub imhfejennof: i32,
|
||||||
#[prost(string, tag = "4")]
|
#[prost(string, tag = "4")]
|
||||||
pub end_timestamp: ::prost::alloc::string::String,
|
pub phkcdmjheen: ::prost::alloc::string::String,
|
||||||
}
|
}
|
||||||
#[derive(proto_gen::CmdID)]
|
#[derive(proto_gen::CmdID)]
|
||||||
#[derive(proto_gen::XorFields)]
|
#[derive(proto_gen::XorFields)]
|
||||||
|
@ -22212,9 +22212,9 @@ pub struct Mbokdhgpobc {
|
||||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||||
pub struct Gacha {
|
pub struct Gacha {
|
||||||
#[prost(uint32, repeated, tag = "1425")]
|
#[prost(uint32, repeated, tag = "1425")]
|
||||||
pub chooseable_up_list: ::prost::alloc::vec::Vec<u32>,
|
pub jkbfeediiho: ::prost::alloc::vec::Vec<u32>,
|
||||||
#[prost(uint32, repeated, tag = "15")]
|
#[prost(uint32, repeated, tag = "15")]
|
||||||
pub up_s_item_list: ::prost::alloc::vec::Vec<u32>,
|
pub nfngmliibdf: ::prost::alloc::vec::Vec<u32>,
|
||||||
#[xor(1098)]
|
#[xor(1098)]
|
||||||
#[prost(uint32, tag = "11")]
|
#[prost(uint32, tag = "11")]
|
||||||
pub obabhacfokn: u32,
|
pub obabhacfokn: u32,
|
||||||
|
@ -22222,7 +22222,7 @@ pub struct Gacha {
|
||||||
pub ioiebkbcnoi: ::prost::alloc::string::String,
|
pub ioiebkbcnoi: ::prost::alloc::string::String,
|
||||||
#[xor(13960)]
|
#[xor(13960)]
|
||||||
#[prost(uint32, tag = "698")]
|
#[prost(uint32, tag = "698")]
|
||||||
pub advanced_s_guarantee: u32,
|
pub gbmmbcbefmp: u32,
|
||||||
#[xor(1462)]
|
#[xor(1462)]
|
||||||
#[prost(uint32, tag = "10")]
|
#[prost(uint32, tag = "10")]
|
||||||
pub kagiddohcmk: u32,
|
pub kagiddohcmk: u32,
|
||||||
|
@ -22230,30 +22230,30 @@ pub struct Gacha {
|
||||||
#[prost(uint32, tag = "1921")]
|
#[prost(uint32, tag = "1921")]
|
||||||
pub nammdglepbk: u32,
|
pub nammdglepbk: u32,
|
||||||
#[prost(message, repeated, tag = "3")]
|
#[prost(message, repeated, tag = "3")]
|
||||||
pub need_item_info_list: ::prost::alloc::vec::Vec<NeedItemInfo>,
|
pub pbalmllekpp: ::prost::alloc::vec::Vec<Feanepokfam>,
|
||||||
#[xor(9792)]
|
#[xor(9792)]
|
||||||
#[prost(int64, tag = "14")]
|
#[prost(int64, tag = "14")]
|
||||||
pub end_timestamp: i64,
|
pub phkcdmjheen: i64,
|
||||||
#[xor(6233)]
|
#[xor(6233)]
|
||||||
#[prost(uint32, tag = "563")]
|
#[prost(uint32, tag = "563")]
|
||||||
pub discount_ten_roll_prize: u32,
|
pub dimlpkbggfc: u32,
|
||||||
#[prost(bool, tag = "1756")]
|
#[prost(bool, tag = "1756")]
|
||||||
pub nkeigieepgn: bool,
|
pub nkeigieepgn: bool,
|
||||||
#[prost(uint32, repeated, tag = "755")]
|
#[prost(uint32, repeated, tag = "755")]
|
||||||
pub free_select_item_list: ::prost::alloc::vec::Vec<u32>,
|
pub hbhckgpjian: ::prost::alloc::vec::Vec<u32>,
|
||||||
#[prost(string, tag = "2")]
|
#[prost(string, tag = "2")]
|
||||||
pub drop_history_webview: ::prost::alloc::string::String,
|
pub mpmnlphpdlk: ::prost::alloc::string::String,
|
||||||
#[xor(4816)]
|
#[xor(4816)]
|
||||||
#[prost(uint32, tag = "13")]
|
#[prost(uint32, tag = "13")]
|
||||||
pub gacha_schedule_id: u32,
|
pub pladpclalgn: u32,
|
||||||
#[xor(11392)]
|
#[xor(11392)]
|
||||||
#[prost(uint32, tag = "574")]
|
#[prost(uint32, tag = "574")]
|
||||||
pub free_select_required_pull: u32,
|
pub fjhglcclbgm: u32,
|
||||||
#[prost(uint32, repeated, tag = "6")]
|
#[prost(uint32, repeated, tag = "6")]
|
||||||
pub up_a_item_list: ::prost::alloc::vec::Vec<u32>,
|
pub goainlmbhnn: ::prost::alloc::vec::Vec<u32>,
|
||||||
#[xor(8288)]
|
#[xor(8288)]
|
||||||
#[prost(uint32, tag = "12")]
|
#[prost(uint32, tag = "12")]
|
||||||
pub gacha_parent_schedule_id: u32,
|
pub dcealmadfgi: u32,
|
||||||
#[xor(8537)]
|
#[xor(8537)]
|
||||||
#[prost(uint32, tag = "1662")]
|
#[prost(uint32, tag = "1662")]
|
||||||
pub pcjdafaaimg: u32,
|
pub pcjdafaaimg: u32,
|
||||||
|
@ -22273,26 +22273,26 @@ pub struct Gacha {
|
||||||
pub gacha_type: u32,
|
pub gacha_type: u32,
|
||||||
#[xor(5057)]
|
#[xor(5057)]
|
||||||
#[prost(uint32, tag = "244")]
|
#[prost(uint32, tag = "244")]
|
||||||
pub s_guarantee: u32,
|
pub ahidoimfiof: u32,
|
||||||
#[xor(1870)]
|
#[xor(1870)]
|
||||||
#[prost(int64, tag = "8")]
|
#[prost(int64, tag = "8")]
|
||||||
pub start_timestamp: i64,
|
pub start_timestamp: i64,
|
||||||
#[xor(562)]
|
#[xor(562)]
|
||||||
#[prost(uint32, tag = "726")]
|
#[prost(uint32, tag = "726")]
|
||||||
pub free_select_progress: u32,
|
pub mkiplhjemoi: u32,
|
||||||
#[xor(8338)]
|
#[xor(8338)]
|
||||||
#[prost(uint32, tag = "96")]
|
#[prost(uint32, tag = "96")]
|
||||||
pub ihjnkoijdgh: u32,
|
pub ihjnkoijdgh: u32,
|
||||||
#[xor(4210)]
|
#[xor(4210)]
|
||||||
#[prost(uint32, tag = "789")]
|
#[prost(uint32, tag = "789")]
|
||||||
pub a_guarantee: u32,
|
pub gokmdbojehm: u32,
|
||||||
#[prost(uint32, repeated, tag = "4")]
|
#[prost(uint32, repeated, tag = "4")]
|
||||||
pub akggbhgkifd: ::prost::alloc::vec::Vec<u32>,
|
pub akggbhgkifd: ::prost::alloc::vec::Vec<u32>,
|
||||||
#[xor(1056)]
|
#[xor(1056)]
|
||||||
#[prost(uint32, tag = "2002")]
|
#[prost(uint32, tag = "2002")]
|
||||||
pub discount_avaliable_num: u32,
|
pub kikannccmmo: u32,
|
||||||
#[prost(string, tag = "7")]
|
#[prost(string, tag = "7")]
|
||||||
pub gacha_info_list_webview: ::prost::alloc::string::String,
|
pub jahbjmphipl: ::prost::alloc::string::String,
|
||||||
#[xor(9009)]
|
#[xor(9009)]
|
||||||
#[prost(uint32, tag = "419")]
|
#[prost(uint32, tag = "419")]
|
||||||
pub ekjlhhdekka: u32,
|
pub ekjlhhdekka: u32,
|
||||||
|
@ -22699,7 +22699,7 @@ pub struct Mcjgjlpjfjc {
|
||||||
pub gebpolkeieh: u32,
|
pub gebpolkeieh: u32,
|
||||||
#[xor(4412)]
|
#[xor(4412)]
|
||||||
#[prost(int64, tag = "1")]
|
#[prost(int64, tag = "1")]
|
||||||
pub end_timestamp: i64,
|
pub phkcdmjheen: i64,
|
||||||
}
|
}
|
||||||
#[derive(proto_gen::CmdID)]
|
#[derive(proto_gen::CmdID)]
|
||||||
#[derive(proto_gen::XorFields)]
|
#[derive(proto_gen::XorFields)]
|
||||||
|
@ -23294,7 +23294,7 @@ pub struct GachaData {
|
||||||
pub cmamhfldihg: ::core::option::Option<Dpgipnmocnj>,
|
pub cmamhfldihg: ::core::option::Option<Dpgipnmocnj>,
|
||||||
#[xor(9369)]
|
#[xor(9369)]
|
||||||
#[prost(uint32, tag = "13")]
|
#[prost(uint32, tag = "13")]
|
||||||
pub random_number: u32,
|
pub madciamhahg: u32,
|
||||||
}
|
}
|
||||||
#[derive(proto_gen::CmdID)]
|
#[derive(proto_gen::CmdID)]
|
||||||
#[derive(proto_gen::XorFields)]
|
#[derive(proto_gen::XorFields)]
|
||||||
|
@ -23399,7 +23399,7 @@ pub struct Nalkdbjimgk {
|
||||||
pub aohakmnfinf: u32,
|
pub aohakmnfinf: u32,
|
||||||
#[xor(2280)]
|
#[xor(2280)]
|
||||||
#[prost(uint32, tag = "4")]
|
#[prost(uint32, tag = "4")]
|
||||||
pub gacha_parent_schedule_id: u32,
|
pub dcealmadfgi: u32,
|
||||||
}
|
}
|
||||||
#[derive(proto_gen::CmdID)]
|
#[derive(proto_gen::CmdID)]
|
||||||
#[cmdid(4620)]
|
#[cmdid(4620)]
|
||||||
|
@ -24021,7 +24021,7 @@ pub struct Himappelgdm {
|
||||||
pub gppmclpnjoe: u32,
|
pub gppmclpnjoe: u32,
|
||||||
#[xor(4244)]
|
#[xor(4244)]
|
||||||
#[prost(uint64, tag = "11")]
|
#[prost(uint64, tag = "11")]
|
||||||
pub end_timestamp: u64,
|
pub phkcdmjheen: u64,
|
||||||
#[xor(6824)]
|
#[xor(6824)]
|
||||||
#[prost(uint32, tag = "7")]
|
#[prost(uint32, tag = "7")]
|
||||||
pub enhandfaalc: u32,
|
pub enhandfaalc: u32,
|
||||||
|
@ -24564,7 +24564,7 @@ pub struct Pahjnbjogon {
|
||||||
pub item_id: u32,
|
pub item_id: u32,
|
||||||
#[xor(1511)]
|
#[xor(1511)]
|
||||||
#[prost(uint32, tag = "1")]
|
#[prost(uint32, tag = "1")]
|
||||||
pub gacha_parent_schedule_id: u32,
|
pub dcealmadfgi: u32,
|
||||||
}
|
}
|
||||||
#[derive(proto_gen::CmdID)]
|
#[derive(proto_gen::CmdID)]
|
||||||
#[derive(proto_gen::XorFields)]
|
#[derive(proto_gen::XorFields)]
|
||||||
|
@ -24653,7 +24653,7 @@ pub struct Eindafcpkce {
|
||||||
#[prost(int32, tag = "1")]
|
#[prost(int32, tag = "1")]
|
||||||
pub nkiegkopoeg: i32,
|
pub nkiegkopoeg: i32,
|
||||||
#[prost(int32, tag = "2")]
|
#[prost(int32, tag = "2")]
|
||||||
pub cost_item_count: i32,
|
pub abalhhfapla: i32,
|
||||||
}
|
}
|
||||||
#[derive(proto_gen::CmdID)]
|
#[derive(proto_gen::CmdID)]
|
||||||
#[cmdid(3276)]
|
#[cmdid(3276)]
|
||||||
|
@ -26395,7 +26395,7 @@ pub struct Pfcmihnfmme {
|
||||||
pub cfnblioopmp: u32,
|
pub cfnblioopmp: u32,
|
||||||
#[xor(13350)]
|
#[xor(13350)]
|
||||||
#[prost(int64, tag = "15")]
|
#[prost(int64, tag = "15")]
|
||||||
pub end_timestamp: i64,
|
pub phkcdmjheen: i64,
|
||||||
#[xor(13289)]
|
#[xor(13289)]
|
||||||
#[prost(int64, tag = "5")]
|
#[prost(int64, tag = "5")]
|
||||||
pub kehdpankopd: i64,
|
pub kehdpankopd: i64,
|
||||||
|
@ -27534,7 +27534,7 @@ pub struct TipsInfo {
|
||||||
pub struct Ieimfkpmegp {
|
pub struct Ieimfkpmegp {
|
||||||
#[xor(378)]
|
#[xor(378)]
|
||||||
#[prost(uint32, tag = "12")]
|
#[prost(uint32, tag = "12")]
|
||||||
pub gacha_parent_schedule_id: u32,
|
pub dcealmadfgi: u32,
|
||||||
#[xor(1946)]
|
#[xor(1946)]
|
||||||
#[prost(uint32, tag = "7")]
|
#[prost(uint32, tag = "7")]
|
||||||
pub avatar_id: u32,
|
pub avatar_id: u32,
|
||||||
|
@ -31149,7 +31149,7 @@ pub struct Bgheihedbcb {
|
||||||
pub apociobpoho: u32,
|
pub apociobpoho: u32,
|
||||||
#[xor(9137)]
|
#[xor(9137)]
|
||||||
#[prost(int64, tag = "2")]
|
#[prost(int64, tag = "2")]
|
||||||
pub end_timestamp: i64,
|
pub phkcdmjheen: i64,
|
||||||
#[prost(bool, tag = "4")]
|
#[prost(bool, tag = "4")]
|
||||||
pub mgfcmlpkjkg: bool,
|
pub mgfcmlpkjkg: bool,
|
||||||
#[xor(438)]
|
#[xor(438)]
|
||||||
|
@ -31470,7 +31470,7 @@ pub struct Nnbooaekcml {
|
||||||
pub ncjcmkgfpej: ::prost::alloc::vec::Vec<Bnpkpfadbdd>,
|
pub ncjcmkgfpej: ::prost::alloc::vec::Vec<Bnpkpfadbdd>,
|
||||||
#[xor(15963)]
|
#[xor(15963)]
|
||||||
#[prost(int32, tag = "13")]
|
#[prost(int32, tag = "13")]
|
||||||
pub need_item_id: i32,
|
pub eijhjbplhih: i32,
|
||||||
}
|
}
|
||||||
#[derive(proto_gen::CmdID)]
|
#[derive(proto_gen::CmdID)]
|
||||||
#[cmdid(816)]
|
#[cmdid(816)]
|
||||||
|
@ -32445,21 +32445,21 @@ pub struct Mkaecbehadi {
|
||||||
#[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 GainItemInfo {
|
pub struct Depahhdodeb {
|
||||||
#[xor(9764)]
|
#[xor(9764)]
|
||||||
#[prost(uint32, tag = "5")]
|
#[prost(uint32, tag = "5")]
|
||||||
pub num: u32,
|
pub num: u32,
|
||||||
#[xor(12647)]
|
#[xor(12647)]
|
||||||
#[prost(uint32, tag = "2")]
|
#[prost(uint32, tag = "2")]
|
||||||
pub extra_item_count: u32,
|
pub ollapieolpm: u32,
|
||||||
#[prost(map = "uint32, uint32", tag = "4")]
|
#[prost(map = "uint32, uint32", tag = "4")]
|
||||||
pub extra_items: ::std::collections::HashMap<u32, u32>,
|
pub nkdheebkjjl: ::std::collections::HashMap<u32, u32>,
|
||||||
#[xor(11858)]
|
#[xor(11858)]
|
||||||
#[prost(uint32, tag = "11")]
|
#[prost(uint32, tag = "11")]
|
||||||
pub item_id: u32,
|
pub item_id: u32,
|
||||||
#[xor(6146)]
|
#[xor(6146)]
|
||||||
#[prost(uint32, tag = "8")]
|
#[prost(uint32, tag = "8")]
|
||||||
pub extra_item_id: u32,
|
pub dghfjhiikkn: u32,
|
||||||
#[xor(10021)]
|
#[xor(10021)]
|
||||||
#[prost(uint32, tag = "9")]
|
#[prost(uint32, tag = "9")]
|
||||||
pub uid: u32,
|
pub uid: u32,
|
||||||
|
@ -32766,7 +32766,7 @@ pub struct Lipadknfagg {
|
||||||
pub abdfdamklia: bool,
|
pub abdfdamklia: bool,
|
||||||
#[xor(1841)]
|
#[xor(1841)]
|
||||||
#[prost(int64, tag = "1696")]
|
#[prost(int64, tag = "1696")]
|
||||||
pub end_timestamp: i64,
|
pub phkcdmjheen: i64,
|
||||||
#[xor(15522)]
|
#[xor(15522)]
|
||||||
#[prost(int64, tag = "14")]
|
#[prost(int64, tag = "14")]
|
||||||
pub jkjhhbpcaon: i64,
|
pub jkjhhbpcaon: i64,
|
||||||
|
@ -32871,7 +32871,7 @@ pub struct Gojokjnppnp {
|
||||||
pub mlfannobjdp: bool,
|
pub mlfannobjdp: bool,
|
||||||
#[xor(1038)]
|
#[xor(1038)]
|
||||||
#[prost(int64, tag = "4")]
|
#[prost(int64, tag = "4")]
|
||||||
pub end_timestamp: i64,
|
pub phkcdmjheen: i64,
|
||||||
#[xor(5317)]
|
#[xor(5317)]
|
||||||
#[prost(uint32, tag = "14")]
|
#[prost(uint32, tag = "14")]
|
||||||
pub ialhcipedom: u32,
|
pub ialhcipedom: u32,
|
||||||
|
|
|
@ -119,77 +119,6 @@ pub struct MainCityModelBin {
|
||||||
}
|
}
|
||||||
#[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 {
|
|
||||||
/// The pity (counting how many pulls) of this pull (in previous record) or the next pull (in status).
|
|
||||||
#[prost(uint32, tag = "1")]
|
|
||||||
pub pity: u32,
|
|
||||||
/// The failure times of this category.
|
|
||||||
#[prost(map = "string, uint32", tag = "2")]
|
|
||||||
pub categories_progress_map: ::std::collections::HashMap<
|
|
||||||
::prost::alloc::string::String,
|
|
||||||
u32,
|
|
||||||
>,
|
|
||||||
/// The selected priority for a Chooseable category.
|
|
||||||
#[prost(map = "string, string", tag = "3")]
|
|
||||||
pub category_chosen_guarantee_map: ::std::collections::HashMap<
|
|
||||||
::prost::alloc::string::String,
|
|
||||||
::prost::alloc::string::String,
|
|
||||||
>,
|
|
||||||
}
|
|
||||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
|
||||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
|
||||||
pub struct GachaExtraItemBin {
|
|
||||||
#[prost(uint32, tag = "1")]
|
|
||||||
pub extra_item_id: u32,
|
|
||||||
#[prost(uint32, tag = "2")]
|
|
||||||
pub extra_item_count: u32,
|
|
||||||
/// How many objects of the main item obtained in gacha is present in the player's bag (before gacha).
|
|
||||||
/// This is used for something like converting when there're extra characters.
|
|
||||||
#[prost(uint32, tag = "3")]
|
|
||||||
pub currently_gained: u32,
|
|
||||||
}
|
|
||||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
|
||||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
|
||||||
pub struct GachaRecordBin {
|
|
||||||
#[prost(int64, tag = "1")]
|
|
||||||
pub pull_timestamp: i64,
|
|
||||||
#[prost(uint32, tag = "2")]
|
|
||||||
pub obtained_item_id: u32,
|
|
||||||
#[prost(uint32, tag = "3")]
|
|
||||||
pub gacha_id: u32,
|
|
||||||
/// The progress BEFORE this gacha is performed. uint32 is rarity.
|
|
||||||
#[prost(map = "uint32, message", tag = "4")]
|
|
||||||
pub progress_map: ::std::collections::HashMap<u32, GachaProgressBin>,
|
|
||||||
#[prost(message, optional, tag = "5")]
|
|
||||||
pub extra_item_bin: ::core::option::Option<GachaExtraItemBin>,
|
|
||||||
}
|
|
||||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
|
||||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
|
||||||
pub struct GachaStatusBin {
|
|
||||||
#[prost(map = "uint32, message", tag = "1")]
|
|
||||||
pub rarity_status_map: ::std::collections::HashMap<u32, GachaProgressBin>,
|
|
||||||
#[prost(map = "string, uint32", tag = "2")]
|
|
||||||
pub discount_usage_map: ::std::collections::HashMap<
|
|
||||||
::prost::alloc::string::String,
|
|
||||||
u32,
|
|
||||||
>,
|
|
||||||
}
|
|
||||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
|
||||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
|
||||||
pub struct GachaModelBin {
|
|
||||||
/// Gacha Status query. string is sharing_guarantee_info_category.
|
|
||||||
#[prost(map = "string, message", tag = "1")]
|
|
||||||
pub gacha_status_map: ::std::collections::HashMap<
|
|
||||||
::prost::alloc::string::String,
|
|
||||||
GachaStatusBin,
|
|
||||||
>,
|
|
||||||
#[prost(message, repeated, tag = "2")]
|
|
||||||
pub gacha_records: ::prost::alloc::vec::Vec<GachaRecordBin>,
|
|
||||||
#[prost(uint32, tag = "3")]
|
|
||||||
pub random_number: u32,
|
|
||||||
}
|
|
||||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
|
||||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
|
||||||
pub struct PlayerDataBin {
|
pub struct PlayerDataBin {
|
||||||
#[prost(message, optional, tag = "1")]
|
#[prost(message, optional, tag = "1")]
|
||||||
pub basic_data_model: ::core::option::Option<BasicDataModelBin>,
|
pub basic_data_model: ::core::option::Option<BasicDataModelBin>,
|
||||||
|
@ -201,6 +130,4 @@ pub struct PlayerDataBin {
|
||||||
pub item_model: ::core::option::Option<ItemModelBin>,
|
pub item_model: ::core::option::Option<ItemModelBin>,
|
||||||
#[prost(message, optional, tag = "5")]
|
#[prost(message, optional, tag = "5")]
|
||||||
pub main_city_model: ::core::option::Option<MainCityModelBin>,
|
pub main_city_model: ::core::option::Option<MainCityModelBin>,
|
||||||
#[prost(message, optional, tag = "6")]
|
|
||||||
pub gacha_model: ::core::option::Option<GachaModelBin>,
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue