Teleport Map & Switching Dynamic Wallpaper Support, Gacha optimization #2

Merged
xeon merged 4 commits from YYHEggEgg/JaneDoe-ZS:master into master 2024-08-06 17:15:05 +00:00
12 changed files with 63267 additions and 826 deletions
Showing only changes of commit bb058b451d - Show all commits

File diff suppressed because it is too large Load diff

View file

@ -168,6 +168,7 @@
], ],
"categories": { "categories": {
"Standard:Bangboo": { "Standard:Bangboo": {
"item_type": "GACHA_ADDED_ITEM_TYPE_BANGBOO",
"item_ids": [ "item_ids": [
54002, // 阿全 54002, // 阿全
54005, // 艾米莉安 54005, // 艾米莉安
@ -180,6 +181,7 @@
"category_weight": 1 "category_weight": 1
}, },
"Event-Exclusive:Bangboo": { "Event-Exclusive:Bangboo": {
"item_type": "GACHA_ADDED_ITEM_TYPE_BANGBOO",
"item_ids": [ "item_ids": [
54004, // 巴特勒 54004, // 巴特勒
], ],
@ -199,6 +201,7 @@
], ],
"categories": { "categories": {
"Standard:Bangboo": { "Standard:Bangboo": {
"item_type": "GACHA_ADDED_ITEM_TYPE_BANGBOO",
"item_ids": [ "item_ids": [
53001, // 企鹅布 53001, // 企鹅布
53003, // 寻宝布 53003, // 寻宝布
@ -224,6 +227,7 @@
"probability_model_tag": "get-B", "probability_model_tag": "get-B",
"categories": { "categories": {
"Standard:W-Engine": { "Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [ "item_ids": [
12001, // 「月相」-望 12001, // 「月相」-望
12002, // 「月相」-晦 12002, // 「月相」-晦
@ -267,6 +271,7 @@
], ],
"categories": { "categories": {
"Standard:Agent": { "Standard:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"item_ids": [ "item_ids": [
1021, // 猫又 1021, // 猫又
1101, // 珂蕾妲 1101, // 珂蕾妲
@ -278,6 +283,7 @@
"category_weight": 50 "category_weight": 50
}, },
"Event-Exclusive:Agent": { "Event-Exclusive:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"is_promotional_items": true, "is_promotional_items": true,
"item_ids": [ "item_ids": [
1191, // 艾莲 1191, // 艾莲
@ -300,6 +306,7 @@
], ],
"categories": { "categories": {
"Standard:Agent": { "Standard:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"item_ids": [ "item_ids": [
1011, // 安比 1011, // 安比
1031, // 妮可 1031, // 妮可
@ -312,6 +319,7 @@
"category_weight": 3525 "category_weight": 3525
}, },
"Event-Exclusive:Agent": { "Event-Exclusive:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"is_promotional_items": true, "is_promotional_items": true,
"item_ids": [ "item_ids": [
1111, // 安东 1111, // 安东
@ -320,6 +328,7 @@
"category_weight": 3525 "category_weight": 3525
}, },
"Standard:W-Engine": { "Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [ "item_ids": [
13001, // 街头巨星 13001, // 街头巨星
13002, // 时光切片 13002, // 时光切片
@ -352,6 +361,7 @@
"probability_model_tag": "get-B", "probability_model_tag": "get-B",
"categories": { "categories": {
"Standard:W-Engine": { "Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [ "item_ids": [
12001, // 「月相」-望 12001, // 「月相」-望
12002, // 「月相」-晦 12002, // 「月相」-晦
@ -398,6 +408,7 @@
], ],
"categories": { "categories": {
"Standard:W-Engine": { "Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [ "item_ids": [
14102, // 钢铁肉垫 14102, // 钢铁肉垫
14110, // 燃狱齿轮 14110, // 燃狱齿轮
@ -409,6 +420,7 @@
"category_weight": 25 "category_weight": 25
}, },
"Event-Exclusive:W-Engine": { "Event-Exclusive:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [ "item_ids": [
14119, // 深海访客 14119, // 深海访客
], ],
@ -433,6 +445,7 @@
], ],
"categories": { "categories": {
"Standard:Agent": { "Standard:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"item_ids": [ "item_ids": [
1011, // 安比 1011, // 安比
1031, // 妮可 1031, // 妮可
@ -447,6 +460,7 @@
"category_weight": 3750 "category_weight": 3750
}, },
"Standard:W-Engine": { "Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [ "item_ids": [
13001, // 街头巨星 13001, // 街头巨星
13002, // 时光切片 13002, // 时光切片
@ -468,6 +482,7 @@
"category_weight": 13125 "category_weight": 13125
}, },
"Event-Exclusive:W-Engine": { "Event-Exclusive:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [ "item_ids": [
13113, // 含羞恶面 13113, // 含羞恶面
13111, // 旋钻机-赤轴 13111, // 旋钻机-赤轴
@ -485,6 +500,7 @@
"probability_model_tag": "get-B", "probability_model_tag": "get-B",
"categories": { "categories": {
"Standard:W-Engine": { "Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [ "item_ids": [
12001, // 「月相」-望 12001, // 「月相」-望
12002, // 「月相」-晦 12002, // 「月相」-晦
@ -528,6 +544,7 @@
], ],
"categories": { "categories": {
"Standard:Agent": { "Standard:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"item_ids": [ "item_ids": [
1021, // 猫又 1021, // 猫又
1101, // 珂蕾妲 1101, // 珂蕾妲
@ -539,6 +556,7 @@
"category_weight": 50 "category_weight": 50
}, },
"Event-Exclusive:Agent": { "Event-Exclusive:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"is_promotional_items": true, "is_promotional_items": true,
"item_ids": [ "item_ids": [
1241 // 朱鸢 1241 // 朱鸢
@ -558,6 +576,7 @@
], ],
"categories": { "categories": {
"Standard:Agent": { "Standard:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"item_ids": [ "item_ids": [
1011, // 安比 1011, // 安比
1061, // 可琳 1061, // 可琳
@ -570,6 +589,7 @@
"category_weight": 3525 "category_weight": 3525
}, },
"Event-Exclusive:Agent": { "Event-Exclusive:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"is_promotional_items": true, "is_promotional_items": true,
"item_ids": [ "item_ids": [
1031, // 妮可 1031, // 妮可
@ -578,6 +598,7 @@
"category_weight": 3525 "category_weight": 3525
}, },
"Standard:W-Engine": { "Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [ "item_ids": [
13001, // 街头巨星 13001, // 街头巨星
13002, // 时光切片 13002, // 时光切片
@ -610,6 +631,7 @@
"probability_model_tag": "get-B", "probability_model_tag": "get-B",
"categories": { "categories": {
"Standard:W-Engine": { "Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [ "item_ids": [
12001, // 「月相」-望 12001, // 「月相」-望
12002, // 「月相」-晦 12002, // 「月相」-晦
@ -653,6 +675,7 @@
], ],
"categories": { "categories": {
"Standard:W-Engine": { "Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [ "item_ids": [
14102, // 钢铁肉垫 14102, // 钢铁肉垫
14110, // 燃狱齿轮 14110, // 燃狱齿轮
@ -664,6 +687,7 @@
"category_weight": 25 "category_weight": 25
}, },
"Event-Exclusive:W-Engine": { "Event-Exclusive:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [ "item_ids": [
14124 // 防暴者VI型 14124 // 防暴者VI型
], ],
@ -683,6 +707,7 @@
], ],
"categories": { "categories": {
"Standard:Agent": { "Standard:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"item_ids": [ "item_ids": [
1011, // 安比 1011, // 安比
1031, // 妮可 1031, // 妮可
@ -697,6 +722,7 @@
"category_weight": 3750 "category_weight": 3750
}, },
"Standard:W-Engine": { "Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [ "item_ids": [
13007, // 正版变身器 13007, // 正版变身器
13002, // 时光切片 13002, // 时光切片
@ -718,6 +744,7 @@
"category_weight": 13125 "category_weight": 13125
}, },
"Event-Exclusive:W-Engine": { "Event-Exclusive:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [ "item_ids": [
13103, // 聚宝箱 13103, // 聚宝箱
13112, // 比格气缸 13112, // 比格气缸
@ -735,6 +762,7 @@
"probability_model_tag": "get-B", "probability_model_tag": "get-B",
"categories": { "categories": {
"Standard:W-Engine": { "Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [ "item_ids": [
12001, // 「月相」-望 12001, // 「月相」-望
12002, // 「月相」-晦 12002, // 「月相」-晦
@ -780,6 +808,7 @@
], ],
"categories": { "categories": {
"Standard:Agent": { "Standard:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"item_ids": [ "item_ids": [
1021, // 猫又 1021, // 猫又
1101, // 珂蕾妲 1101, // 珂蕾妲
@ -791,6 +820,7 @@
"category_weight": 50 "category_weight": 50
}, },
"Event-Exclusive:Agent": { "Event-Exclusive:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"is_promotional_items": true, "is_promotional_items": true,
"item_ids": [ "item_ids": [
1251, // 青衣 1251, // 青衣
@ -810,6 +840,7 @@
], ],
"categories": { "categories": {
"Standard:Agent": { "Standard:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"item_ids": [ "item_ids": [
1011, // 安比 1011, // 安比
1031, // 妮可 1031, // 妮可
@ -823,6 +854,7 @@
"category_weight": 3525 "category_weight": 3525
}, },
"Event-Exclusive:Agent": { "Event-Exclusive:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"is_promotional_items": true, "is_promotional_items": true,
"item_ids": [ "item_ids": [
1131, // 苍角 1131, // 苍角
@ -831,6 +863,7 @@
"category_weight": 3525 "category_weight": 3525
}, },
"Standard:W-Engine": { "Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [ "item_ids": [
13001, // 街头巨星 13001, // 街头巨星
13002, // 时光切片 13002, // 时光切片
@ -864,6 +897,7 @@
"probability_model_tag": "get-B", "probability_model_tag": "get-B",
"categories": { "categories": {
"Standard:W-Engine": { "Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [ "item_ids": [
12001, // 「月相」-望 12001, // 「月相」-望
12002, // 「月相」-晦 12002, // 「月相」-晦
@ -907,6 +941,7 @@
], ],
"categories": { "categories": {
"Standard:W-Engine": { "Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [ "item_ids": [
14102, // 钢铁肉垫 14102, // 钢铁肉垫
14110, // 燃狱齿轮 14110, // 燃狱齿轮
@ -918,6 +953,7 @@
"category_weight": 25 "category_weight": 25
}, },
"Event-Exclusive:W-Engine": { "Event-Exclusive:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [ "item_ids": [
14125, // 玉壶青冰 14125, // 玉壶青冰
], ],
@ -937,6 +973,7 @@
], ],
"categories": { "categories": {
"Standard:Agent": { "Standard:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"item_ids": [ "item_ids": [
1011, // 安比 1011, // 安比
1031, // 妮可 1031, // 妮可
@ -949,6 +986,7 @@
"category_weight": 3750 "category_weight": 3750
}, },
"Standard:W-Engine": { "Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [ "item_ids": [
13001, // 街头巨星 13001, // 街头巨星
13002, // 时光切片 13002, // 时光切片
@ -971,6 +1009,7 @@
"category_weight": 13125 "category_weight": 13125
}, },
"Event-Exclusive:W-Engine": { "Event-Exclusive:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [ "item_ids": [
13113, // 含羞恶面 13113, // 含羞恶面
13127, // 维序者-特化型 13127, // 维序者-特化型
@ -988,6 +1027,7 @@
"probability_model_tag": "get-B", "probability_model_tag": "get-B",
"categories": { "categories": {
"Standard:W-Engine": { "Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [ "item_ids": [
12001, // 「月相」-望 12001, // 「月相」-望
12002, // 「月相」-晦 12002, // 「月相」-晦
@ -1031,6 +1071,7 @@
], ],
"categories": { "categories": {
"Standard:Agent": { "Standard:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"item_ids": [ "item_ids": [
1021, // 猫又 1021, // 猫又
1101, // 珂蕾妲 1101, // 珂蕾妲
@ -1042,6 +1083,7 @@
"category_weight": 50 "category_weight": 50
}, },
"Event-Exclusive:Agent": { "Event-Exclusive:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"is_promotional_items": true, "is_promotional_items": true,
"item_ids": [ "item_ids": [
1261, // 简 1261, // 简
@ -1061,6 +1103,7 @@
], ],
"categories": { "categories": {
"Standard:Agent": { "Standard:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"item_ids": [ "item_ids": [
1011, // 安比 1011, // 安比
1031, // 妮可 1031, // 妮可
@ -1074,6 +1117,7 @@
"category_weight": 3525 "category_weight": 3525
}, },
"Event-Exclusive:Agent": { "Event-Exclusive:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"is_promotional_items": true, "is_promotional_items": true,
"item_ids": [ "item_ids": [
1271, // 赛斯 1271, // 赛斯
@ -1082,6 +1126,7 @@
"category_weight": 3525 "category_weight": 3525
}, },
"Standard:W-Engine": { "Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [ "item_ids": [
13001, // 街头巨星 13001, // 街头巨星
13002, // 时光切片 13002, // 时光切片
@ -1115,6 +1160,7 @@
"probability_model_tag": "get-B", "probability_model_tag": "get-B",
"categories": { "categories": {
"Standard:W-Engine": { "Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [ "item_ids": [
12001, // 「月相」-望 12001, // 「月相」-望
12002, // 「月相」-晦 12002, // 「月相」-晦
@ -1158,6 +1204,7 @@
], ],
"categories": { "categories": {
"Standard:W-Engine": { "Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [ "item_ids": [
14102, // 钢铁肉垫 14102, // 钢铁肉垫
14110, // 燃狱齿轮 14110, // 燃狱齿轮
@ -1169,6 +1216,7 @@
"category_weight": 25 "category_weight": 25
}, },
"Event-Exclusive:W-Engine": { "Event-Exclusive:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [ "item_ids": [
14126, // 淬锋钳刺 14126, // 淬锋钳刺
], ],
@ -1188,6 +1236,7 @@
], ],
"categories": { "categories": {
"Standard:Agent": { "Standard:Agent": {
"item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER",
"item_ids": [ "item_ids": [
1011, // 安比 1011, // 安比
1031, // 妮可 1031, // 妮可
@ -1200,6 +1249,7 @@
"category_weight": 3750 "category_weight": 3750
}, },
"Standard:W-Engine": { "Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [ "item_ids": [
13001, // 街头巨星 13001, // 街头巨星
13002, // 时光切片 13002, // 时光切片
@ -1222,6 +1272,7 @@
"category_weight": 13125 "category_weight": 13125
}, },
"Event-Exclusive:W-Engine": { "Event-Exclusive:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [ "item_ids": [
13127, // 维序者-特化型 13127, // 维序者-特化型
13113, // 含羞恶面 13113, // 含羞恶面
@ -1239,6 +1290,7 @@
"probability_model_tag": "get-B", "probability_model_tag": "get-B",
"categories": { "categories": {
"Standard:W-Engine": { "Standard:W-Engine": {
"item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON",
"item_ids": [ "item_ids": [
12001, // 「月相」-望 12001, // 「月相」-望
12002, // 「月相」-晦 12002, // 「月相」-晦

View file

@ -1,7 +1,6 @@
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use chrono::{prelude::Local, DateTime}; use chrono::{prelude::Local, DateTime};
use proto::GachaAddedItemType;
use serde::{Deserialize, Deserializer}; use serde::{Deserialize, Deserializer};
use tracing; use tracing;
@ -152,13 +151,64 @@ impl DiscountPolicyCollection {
} }
} }
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum GachaAddedItemType {
#[default]
None = 0,
Weapon = 1,
Character = 2,
Bangboo = 3,
}
impl GachaAddedItemType {
pub fn as_str_name(&self) -> &'static str {
match self {
GachaAddedItemType::None => "GACHA_ADDED_ITEM_TYPE_NONE",
GachaAddedItemType::Weapon => "GACHA_ADDED_ITEM_TYPE_WEAPON",
GachaAddedItemType::Character => "GACHA_ADDED_ITEM_TYPE_CHARACTER",
GachaAddedItemType::Bangboo => "GACHA_ADDED_ITEM_TYPE_BANGBOO",
}
}
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
match value {
"GACHA_ADDED_ITEM_TYPE_NONE" => Some(Self::None),
"GACHA_ADDED_ITEM_TYPE_WEAPON" => Some(Self::Weapon),
"GACHA_ADDED_ITEM_TYPE_CHARACTER" => Some(Self::Character),
"GACHA_ADDED_ITEM_TYPE_BANGBOO" => Some(Self::Bangboo),
_ => None,
}
}
}
impl From<i32> for GachaAddedItemType {
fn from(value: i32) -> Self {
match value {
1 => Self::Weapon,
2 => Self::Character,
3 => Self::Bangboo,
_ => Self::None
}
}
}
impl Into<i32> for GachaAddedItemType {
fn into(self) -> i32 {
match self {
Self::Weapon => 1,
Self::Character => 2,
Self::Bangboo => 3,
Self::None => 0,
}
}
}
#[derive(Debug, Default, Deserialize)] #[derive(Debug, Default, Deserialize)]
pub struct GachaCategoryInfo { pub struct GachaCategoryInfo {
#[serde(default)] #[serde(default)]
pub is_promotional_items: bool, pub is_promotional_items: bool,
pub item_ids: Vec<u32>, pub item_ids: Vec<u32>,
pub category_weight: u32, pub category_weight: u32,
#[serde(default, deserialize_with = "from_str")] #[serde(deserialize_with = "from_str")]
pub item_type: GachaAddedItemType, pub item_type: GachaAddedItemType,
} }

View file

@ -0,0 +1,10 @@
use serde::Deserialize;
template_id!(Item u32 id);
#[derive(Deserialize, Debug)]
#[serde(rename_all = "PascalCase")]
pub struct ItemTemplate {
#[serde(rename = "ID")]
pub id: ItemID,
}

View file

@ -97,4 +97,5 @@ template_tables! {
QuickAccessTemplate; QuickAccessTemplate;
QuickFuncTemplate; QuickFuncTemplate;
TeleportConfigTemplate; TeleportConfigTemplate;
ItemTemplate;
} }

View file

@ -1,19 +1,18 @@
use std::{
cmp::min,
collections::{
hash_map::Entry::{Occupied, Vacant},
HashSet,
},
};
use data::{ use data::{
gacha::{gacha_config::*, global_gacha_config}, gacha::{
tables::{AvatarBaseID, WeaponID}, gacha_config::{CharacterGachaPool, GachaAddedItemType},
global_gacha_config,
},
tables::{AvatarBaseID, ItemID, WeaponID},
}; };
use proto::{GainItemInfo, GetGachaDataScRsp};
use super::*; use super::*;
use crate::{
handlers::core::NetError,
logic::{item::ItemModel, role::RoleModel},
};
use chrono::{DateTime, Local}; use chrono::{DateTime, Local};
use proto::*;
pub async fn on_get_gacha_data( pub async fn on_get_gacha_data(
_session: &NetSession, _session: &NetSession,
@ -32,7 +31,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, &Local::now())), gacha_data: Some(_player.gacha_model.to_client(&Local::now())),
}) })
} }
} }
@ -51,101 +50,58 @@ pub async fn on_do_gacha(
&gachaconf.character_gacha_pool_list, &gachaconf.character_gacha_pool_list,
&req.gacha_parent_schedule_id, &req.gacha_parent_schedule_id,
&pull_time, &pull_time,
); )?;
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 {
retcode: Retcode::RetFail.into(),
..Default::default()
});
};
let target_pool = target_pool.unwrap();
// tracing::info!("cost_item_count: {}", req.cost_item_count); // tracing::info!("cost_item_count: {}", req.cost_item_count);
let mut pull_count = if req.cost_item_count > 1 { 10 } else { 1 }; let pull_count = if req.cost_item_count > 1 { 10 } else { 1 };
let cost_count = pull_count; let mut cost_count = gacha_model.get_actual_cost_count(target_pool, &pull_count);
if pull_count != req.cost_item_count {
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;
// cost_count = discount_policy.discounted_prize;
}
}
}
if cost_count != req.cost_item_count {
tracing::info!( tracing::info!(
"refuse gacha because: expected cost item {cost_count}, found {}", "refuse gacha because: expected cost item {cost_count}, found {}",
req.cost_item_count req.cost_item_count
); );
return Ok(DoGachaScRsp { return Err(NetError::from(Retcode::RetFail));
retcode: Retcode::RetFail.into(),
..Default::default()
});
} else { } else {
// TODO: cost resource // TODO: cost resource
} }
let mut gain_item_list: Vec<GainItemInfo> = vec![]; let mut gain_item_list: Vec<GainItemInfo> = vec![];
while pull_count > 0 { while cost_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.clone().unwrap();
let uid = match GachaAddedItemType::try_from(pull_result.item_type) { let uid = add_item(
Ok(enum_val) => match enum_val { role_model,
GachaAddedItemType::Weapon => match WeaponID::new(pull_result.obtained_item_id) { item_model,
Some(id) => item_model.add_weapon(id).value(), &pull_result.obtained_item_id,
None => 0, &pull_result.item_type,
}, )?;
GachaAddedItemType::Character => { let (mut extra_item_id, mut extra_item_count) = (0, 0);
match AvatarBaseID::new(pull_result.obtained_item_id) { if let Some(extra_resources) = &pull_result.extra_resources {
Some(id) => { (extra_item_id, extra_item_count) = (
role_model.add_avatar(id); extra_resources.extra_item_id.value(),
0 extra_resources.extra_item_count.clone(),
}
None => 0,
}
}
_ => 0,
},
Err(_err) => 0,
};
if extra_item_bin.extra_item_id != 0 {
item_model.add_resource(
extra_item_bin.extra_item_id,
extra_item_bin.extra_item_count,
); );
item_model.add_resource(extra_item_id, extra_item_count);
} }
gain_item_list.push(GainItemInfo { gain_item_list.push(GainItemInfo {
item_id: pull_result.obtained_item_id, item_id: pull_result.obtained_item_id.value(),
extra_item_id: extra_item_bin.extra_item_id, extra_item_id,
extra_item_count: extra_item_bin.extra_item_count, extra_item_count,
uid, uid,
num: 1, num: 1,
..GainItemInfo::default() ..GainItemInfo::default()
}); });
pull_count -= 1; cost_count -= 1;
gacha_model.gacha_bin.gacha_records.push(pull_result); gacha_model.gacha_records.push(pull_result);
} }
_session
.notify(construct_sync(role_model, item_model))
.await?;
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, &pull_time)), gacha_data: Some(gacha_model.to_client(&pull_time)),
cost_item_count: req.cost_item_count, cost_item_count: req.cost_item_count,
}) })
} }
@ -156,123 +112,31 @@ pub async fn on_gacha_free_agent(
req: GachaFreeAgentCsReq, req: GachaFreeAgentCsReq,
) -> NetResult<GachaFreeAgentScRsp> { ) -> NetResult<GachaFreeAgentScRsp> {
let gachaconf = global_gacha_config(); let gachaconf = global_gacha_config();
let gacha_model = &mut _player.gacha_model;
let role_model = &mut _player.role_model; let role_model = &mut _player.role_model;
let item_model = &mut _player.item_model;
let pull_time = Local::now(); let pull_time = Local::now();
let target_pool = get_gacha_pool( let target_pool = get_gacha_pool(
&gachaconf.character_gacha_pool_list, &gachaconf.character_gacha_pool_list,
&req.gacha_parent_schedule_id, &req.gacha_parent_schedule_id,
&pull_time, &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 item_id = ItemID::new(req.avatar_id);
let mut free_select_progress: u32 = 0; if let None = item_id {
let mut free_select_required_pull: u32 = 0; return Err(NetError::from(Retcode::RetFail));
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);
}
} }
let item_id = item_id.unwrap();
if let None = free_select_policy { let item_type = gacha_model.request_free_agent(target_pool, &item_id);
tracing::info!( if item_type == GachaAddedItemType::None {
"refuse free agent because: pool of parent_schedule_id {} hasn't defined free agent discount yet (or used up chance)", return Err(NetError::from(Retcode::RetFail));
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()
});
} }
let _ = add_item(role_model, item_model, &item_id, &item_type);
role_model.add_avatar(AvatarBaseID::new(req.avatar_id).unwrap()); _session
(*status_bin .notify(construct_sync(role_model, item_model))
.discount_usage_map .await?;
.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 { Ok(GachaFreeAgentScRsp {
retcode: Retcode::RetSucc.into(), retcode: Retcode::RetSucc.into(),
}) })
@ -290,330 +154,82 @@ pub async fn on_choose_gacha_up(
&gachaconf.character_gacha_pool_list, &gachaconf.character_gacha_pool_list,
&req.gacha_parent_schedule_id, &req.gacha_parent_schedule_id,
&pull_time, &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() { let item_id = ItemID::new(req.item_id);
if rarity_items.rarity != gachaconf.common_properties.s_item_rarity { if let None = item_id {
continue; return Err(NetError::from(Retcode::RetFail));
}
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()
});
}
} }
let item_id = item_id.unwrap();
Ok(ChooseGachaUpScRsp { Ok(ChooseGachaUpScRsp {
retcode: Retcode::RetFail.into(), retcode: if gacha_model.choose_gacha_up(target_pool, &item_id) {
Retcode::RetSucc.into()
} else {
Retcode::RetFail.into()
},
..Default::default() ..Default::default()
}) })
} }
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 discount_ten_roll_prize: u32 = 0;
let mut discount_avaliable_num: u32 = 0;
let mut advanced_s_guarantee: u32 = 0;
let mut free_select_progress: u32 = 0;
let mut free_select_required_pull: u32 = 0;
let mut free_select_policy: Option<&FreeSelectItem> = None;
for discount_policy_tag in target_pool.discount_policy_tags.iter() {
if common_properties.newcomer_advanced_s_tag == *discount_policy_tag {
let policy = gachaconf
.discount_policies
.advanced_guarantee_map
.get(discount_policy_tag)
.unwrap();
if status_bin
.discount_usage_map
.get(discount_policy_tag)
.unwrap()
< &policy.use_limit
{
advanced_s_guarantee = policy.guarantee_pity - pity_s + 1;
}
} else if common_properties.ten_pull_discount_tag == *discount_policy_tag {
let policy = gachaconf
.discount_policies
.ten_pull_discount_map
.get(discount_policy_tag)
.unwrap();
let discount_usage = status_bin
.discount_usage_map
.get(discount_policy_tag)
.unwrap();
if discount_usage < &policy.use_limit {
discount_ten_roll_prize = policy.discounted_prize;
discount_avaliable_num = policy.use_limit - discount_usage;
}
} else if gachaconf
.discount_policies
.free_select_map
.contains_key(discount_policy_tag)
{
let policy = gachaconf
.discount_policies
.free_select_map
.get(discount_policy_tag)
.unwrap();
let free_select_demand_idx = usize::try_from(
*(status_bin
.discount_usage_map
.get(&policy.free_select_usage_record_tag)
.unwrap()),
)
.unwrap();
if policy.milestones.len() <= free_select_demand_idx {
continue;
}
let free_select_actual_progress = status_bin
.discount_usage_map
.get(&policy.free_select_progress_record_tag)
.unwrap();
free_select_policy = Some(policy);
free_select_required_pull = policy
.milestones
.get(free_select_demand_idx)
.unwrap()
.to_owned();
free_select_progress = min(free_select_required_pull, *free_select_actual_progress);
}
}
let mut up_s_item_list: Vec<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![];
}
// }
}
}
let need_item_info_list: Vec<NeedItemInfo> = vec![NeedItemInfo {
need_item_id: target_pool.cost_item_id,
need_item_count: 1,
}];
let mut result = Gacha {
gacha_schedule_id: target_pool.gacha_schedule_id,
gacha_parent_schedule_id: target_pool.gacha_parent_schedule_id,
gacha_type: target_pool.gacha_type,
start_timestamp: target_pool.start_time.timestamp(),
end_timestamp: target_pool.end_time.timestamp(),
discount_avaliable_num,
discount_ten_roll_prize,
advanced_s_guarantee,
s_guarantee,
a_guarantee,
need_item_info_list,
free_select_progress,
free_select_required_pull,
free_select_item_list,
chosen_up_item,
// nammdglepbk: 563,
// hgmcofcjmbg: 101,
// akggbhgkifd: chooseable_up_list.clone(),
chooseable_up_list,
..Gacha::default()
};
if up_s_item_list.len() > 0 {
result.up_s_item_list = up_s_item_list;
}
if up_a_item_list.len() > 0 {
result.up_a_item_list = up_a_item_list;
}
result
}
fn generate_all_gacha_info(_player: &Player, now: &DateTime<Local>) -> 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() {
if target_pool.is_still_open(now) {
gacha_list.push(generate_gacha_info_from_pool(
&gacha_bin,
target_pool,
&gachaconf.common_properties,
));
}
}
// tracing::info!("gacha_list: {:?}", gacha_list);
GachaData {
random_number: 6167,
gacha_pool: Some(GachaPool { gacha_list }),
..GachaData::default()
}
}
fn get_gacha_pool<'conf>( fn get_gacha_pool<'conf>(
character_gacha_pool_list: &'conf Vec<CharacterGachaPool>, character_gacha_pool_list: &'conf Vec<CharacterGachaPool>,
gacha_parent_schedule_id: &u32, gacha_parent_schedule_id: &u32,
pull_time: &DateTime<Local>, pull_time: &DateTime<Local>,
) -> Option<&'conf CharacterGachaPool> { ) -> NetResult<&'conf CharacterGachaPool> {
for target_pool in character_gacha_pool_list.iter() { for target_pool in character_gacha_pool_list.iter() {
if &target_pool.gacha_parent_schedule_id == gacha_parent_schedule_id if &target_pool.gacha_parent_schedule_id == gacha_parent_schedule_id
&& target_pool.is_still_open(pull_time) && target_pool.is_still_open(pull_time)
{ {
return Some(target_pool); return Ok(target_pool);
} }
} }
None tracing::info!(
"refuse gacha op because: pool of parent_schedule_id {} not found or isn't in open time",
gacha_parent_schedule_id
);
Err(NetError::from(Retcode::RetFail))
}
/// Return is item UID (weapon specific)
fn add_item(
role_model: &mut RoleModel,
item_model: &mut ItemModel,
item_id: &ItemID,
item_type: &GachaAddedItemType,
) -> NetResult<u32> {
match item_type {
GachaAddedItemType::Character => match AvatarBaseID::new(item_id.value()) {
Some(avatar_id) => {
role_model.add_avatar(avatar_id);
Ok(0)
}
None => {
tracing::info!("add item failed for avatar id {item_id}");
Err(NetError::from(Retcode::RetFail))
}
},
GachaAddedItemType::Weapon => match WeaponID::new(item_id.value()) {
Some(weapon_id) => Ok(item_model.add_weapon(weapon_id).value()),
None => {
tracing::info!("add item failed for weapon id {item_id}");
Err(NetError::from(Retcode::RetFail))
}
},
GachaAddedItemType::Bangboo => Ok(0),
_ => {
tracing::info!(
"add item failed due to undefined item type (from {item_id}) in configuration"
);
Err(NetError::from(Retcode::RetFail))
}
}
}
fn construct_sync(role_model: &RoleModel, item_model: &ItemModel) -> PlayerSyncScNotify {
PlayerSyncScNotify {
avatar: Some(role_model.avatar_sync()),
item_sync: Some(item_model.item_sync()),
..Default::default()
}
} }

View file

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

View file

@ -1,20 +1,25 @@
use super::record::*;
use super::stat::*;
use data::gacha; use data::gacha;
use data::gacha::gacha_config::*; use data::gacha::gacha_config::*;
use proto::*; use data::tables::ItemID;
use chrono::{DateTime, Local}; use chrono::{DateTime, Local};
use proto::GachaModelBin;
use rand::{thread_rng, Rng}; use rand::{thread_rng, Rng};
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::hash::{BuildHasher, Hash}; use std::hash::{BuildHasher, Hash};
pub struct GachaModel { pub struct GachaModel {
pub gacha_bin: GachaModelBin, pub gacha_status_map: HashMap<String, GachaStatus>,
pub gacha_records: Vec<GachaRecord>,
} }
impl Default for GachaModel { impl Default for GachaModel {
fn default() -> GachaModel { fn default() -> GachaModel {
let result = GachaModel { let result = GachaModel {
gacha_bin: GachaModelBin::default(), gacha_status_map: HashMap::new(),
gacha_records: vec![],
}; };
result.post_deserialize() result.post_deserialize()
} }
@ -22,19 +27,37 @@ impl Default for GachaModel {
impl GachaModel { impl GachaModel {
pub fn from_bin(gacha_bin: GachaModelBin) -> Self { pub fn from_bin(gacha_bin: GachaModelBin) -> Self {
let result = Self { gacha_bin }; let result = Self {
gacha_status_map: gacha_bin
.gacha_status_map
.into_iter()
.map(|(k, v)| (k, GachaStatus::from_bin(v)))
.collect(),
gacha_records: gacha_bin
.gacha_records
.into_iter()
.map(|x| GachaRecord::from_bin(x))
.collect(),
};
result.post_deserialize() result.post_deserialize()
} }
pub fn to_bin(&self) -> GachaModelBin { pub fn to_bin(&self) -> GachaModelBin {
self.gacha_bin.clone() GachaModelBin {
gacha_status_map: self
.gacha_status_map
.iter()
.map(|(k, v)| (k.clone(), v.to_bin()))
.collect(),
gacha_records: self.gacha_records.iter().map(|x| x.to_bin()).collect(),
..Default::default()
}
} }
pub fn post_deserialize(mut self) -> GachaModel { pub fn post_deserialize(mut self) -> GachaModel {
let gachaconf = gacha::global_gacha_config(); let gachaconf = gacha::global_gacha_config();
for gacha_pool in gachaconf.character_gacha_pool_list.iter() { for gacha_pool in gachaconf.character_gacha_pool_list.iter() {
let gacha_comp_bin = &mut self.gacha_bin; let mut gacha_status_map = &mut self.gacha_status_map;
let mut gacha_status_map = &mut gacha_comp_bin.gacha_status_map;
let status_bin = get_or_add( let status_bin = get_or_add(
&mut gacha_status_map, &mut gacha_status_map,
&gacha_pool.sharing_guarantee_info_category, &gacha_pool.sharing_guarantee_info_category,
@ -97,11 +120,11 @@ impl GachaModel {
&'bin mut self, &'bin mut self,
pull_time: &DateTime<Local>, pull_time: &DateTime<Local>,
target_pool: &'conf CharacterGachaPool, target_pool: &'conf CharacterGachaPool,
) -> GachaRecordBin { ) -> GachaRecord {
let mut gacha_bin = &mut self.gacha_bin;
let (rarity_items, progress_bin, status_bin, probability_model) = let (rarity_items, progress_bin, status_bin, probability_model) =
determine_rarity(&gacha_bin, target_pool); self.determine_rarity(target_pool);
let (category_tag, category) = determine_category(rarity_items, progress_bin, target_pool); let (category_tag, category) =
self.determine_category(rarity_items, progress_bin, target_pool);
let result = determine_gacha_result( let result = determine_gacha_result(
pull_time, pull_time,
category, category,
@ -110,11 +133,311 @@ impl GachaModel {
progress_bin, progress_bin,
rarity_items, rarity_items,
); );
update_pity(&mut gacha_bin, rarity_items, probability_model, target_pool); self.update_pity(rarity_items, probability_model, target_pool);
update_category_guarantee_info(&mut gacha_bin, rarity_items, &category_tag, target_pool); self.update_category_guarantee_info(rarity_items, &category_tag, target_pool);
update_discount(&mut gacha_bin, target_pool, &category_tag, rarity_items); self.update_discount(target_pool, &category_tag, rarity_items);
result result
} }
fn rand_rarity<'bin, 'conf>(
&'bin self,
target_pool: &'conf CharacterGachaPool,
status_bin: &'bin GachaStatus,
) -> (
&'conf GachaAvailableItemsInfo,
&'bin GachaProgress,
&'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>(
&'bin self,
target_pool: &'conf CharacterGachaPool,
) -> (
&'conf GachaAvailableItemsInfo,
&'bin GachaProgress,
&'bin GachaStatus,
&'conf ProbabilityModel,
) {
let gachaconf = gacha::global_gacha_config();
let status_bin = self
.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) =
self.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>(
&'bin self,
rarity_items: &'conf GachaAvailableItemsInfo,
progress_bin: &'bin GachaProgress,
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 is SELECTED then we MUST give that category's item.
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;
}
// 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.
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 update_pity<'bin, 'conf>(
&'bin mut self,
rarity_items: &'conf GachaAvailableItemsInfo,
probability_model: &'conf ProbabilityModel,
target_pool: &'conf CharacterGachaPool,
) {
let status_bin = self
.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>(
&'bin mut self,
rarity_items: &'conf GachaAvailableItemsInfo,
category_tag: &String,
target_pool: &'conf CharacterGachaPool,
) {
let gachaconf = gacha::global_gacha_config();
let status_bin = self
.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>(
&'bin mut self,
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 = self
.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 = self
.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.free_select_map.iter() {
if !target_pool.discount_policy_tags.contains(policy_tag) {
continue;
}
let status_bin = self
.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;
}
}
} }
fn get_or_add<'a, K: Eq + PartialEq + Hash + Clone, V: Default, S: BuildHasher>( fn get_or_add<'a, K: Eq + PartialEq + Hash + Clone, V: Default, S: BuildHasher>(
@ -127,200 +450,14 @@ fn get_or_add<'a, K: Eq + PartialEq + Hash + Clone, V: Default, S: BuildHasher>(
map.get_mut(key).unwrap() 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 is SELECTED then we MUST give that category's item.
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;
}
// 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.
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>( fn determine_gacha_result<'bin, 'conf>(
pull_time: &DateTime<Local>, pull_time: &DateTime<Local>,
category: &'conf GachaCategoryInfo, category: &'conf GachaCategoryInfo,
target_pool: &'conf CharacterGachaPool, target_pool: &'conf CharacterGachaPool,
status_bin: &'bin GachaStatusBin, status_bin: &'bin GachaStatus,
progress_bin: &'bin GachaProgressBin, progress_bin: &'bin GachaProgress,
rarity_items: &'conf GachaAvailableItemsInfo, rarity_items: &'conf GachaAvailableItemsInfo,
) -> GachaRecordBin { ) -> GachaRecord {
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 mut item_id: Option<&u32> = None; let mut item_id: Option<&u32> = None;
@ -361,7 +498,7 @@ fn determine_gacha_result<'bin, 'conf>(
.get(rand::thread_rng().gen_range(0..item_pool_len) as usize) .get(rand::thread_rng().gen_range(0..item_pool_len) as usize)
.unwrap(), .unwrap(),
}; };
let mut extra_item_id: u32 = 0; let mut extra_item_id: Option<ItemID> = None;
let mut extra_item_count: u32 = 0; let mut extra_item_count: u32 = 0;
for extra_items_policy_tag in rarity_items.extra_items_policy_tags.iter() { for extra_items_policy_tag in rarity_items.extra_items_policy_tags.iter() {
@ -372,134 +509,23 @@ fn determine_gacha_result<'bin, 'conf>(
// 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. // 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 = ItemID::new(extra_items_policy.id);
extra_item_count = extra_items_policy.count; extra_item_count = extra_items_policy.count;
} }
} }
let extra_item_bin = GachaExtraItemBin { let extra_resources = match extra_item_id {
extra_item_id, Some(item_id) => Some(GachaExtraResources {
extra_item_count, extra_item_id: item_id,
currently_gained: 0, extra_item_count,
}),
None => None,
}; };
GachaRecordBin { GachaRecord {
pull_timestamp: pull_time.timestamp(), pull_timestamp: pull_time.timestamp(),
obtained_item_id: item_id.clone(), obtained_item_id: ItemID::new_unchecked(item_id.clone()),
gacha_id: target_pool.gacha_schedule_id.clone(), gacha_id: target_pool.gacha_schedule_id.clone(),
progress_map: status_bin.rarity_status_map.clone(), progress_map: status_bin.rarity_status_map.clone(),
extra_item_bin: Some(extra_item_bin), extra_resources,
item_type: category.item_type.into(), item_type: category.item_type.clone(),
}
}
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;
}
}
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

@ -1,4 +1,6 @@
mod client_op;
mod gacha_model; mod gacha_model;
mod record; mod record;
mod stat;
pub use gacha_model::GachaModel; pub use gacha_model::GachaModel;

View file

@ -0,0 +1,85 @@
use data::gacha::gacha_config::*;
use data::tables::ItemID;
use proto::{GachaExtraItemBin, GachaRecordBin};
use std::collections::HashMap;
use super::stat::*;
#[derive(Debug, Clone)]
pub struct GachaExtraResources {
pub extra_item_id: ItemID,
pub extra_item_count: u32,
}
impl GachaExtraResources {
pub fn from_bin_opt(bin_opt: Option<GachaExtraItemBin>) -> Option<Self> {
match bin_opt {
None => None,
Some(bin) => {
let item_id_opt = ItemID::new(bin.extra_item_id);
match item_id_opt {
None => None,
Some(extra_item_id) => Some(Self {
extra_item_id,
extra_item_count: bin.extra_item_count,
}),
}
}
}
}
pub fn to_bin_opt(opt: &Option<Self>) -> Option<GachaExtraItemBin> {
match opt {
None => None,
Some(this) => Some(GachaExtraItemBin {
extra_item_id: this.extra_item_id.value(),
extra_item_count: this.extra_item_count.clone(),
..Default::default()
}),
}
}
}
#[derive(Debug, Clone)]
pub struct GachaRecord {
pub pull_timestamp: i64,
pub obtained_item_id: ItemID,
pub gacha_id: u32,
/// The progress BEFORE this gacha is performed. key is rarity.
pub progress_map: HashMap<u32, GachaProgress>,
pub extra_resources: Option<GachaExtraResources>,
pub item_type: GachaAddedItemType,
}
impl GachaRecord {
pub fn from_bin(bin: GachaRecordBin) -> Self {
Self {
pull_timestamp: bin.pull_timestamp,
obtained_item_id: ItemID::new_unchecked(bin.obtained_item_id),
gacha_id: bin.gacha_id,
progress_map: bin
.progress_map
.into_iter()
.map(|(k, v)| (k, GachaProgress::from_bin(v)))
.collect(),
extra_resources: GachaExtraResources::from_bin_opt(bin.extra_item_bin),
item_type: GachaAddedItemType::from(bin.item_type),
}
}
pub fn to_bin(&self) -> GachaRecordBin {
GachaRecordBin {
pull_timestamp: self.pull_timestamp.clone(),
obtained_item_id: self.obtained_item_id.value(),
gacha_id: self.gacha_id.clone(),
progress_map: self
.progress_map
.iter()
.map(|(k, v)| (k.clone(), v.to_bin()))
.collect(),
extra_item_bin: GachaExtraResources::to_bin_opt(&self.extra_resources),
item_type: self.item_type.clone().into(),
}
}
}

View file

@ -0,0 +1,73 @@
use proto::*;
use std::collections::HashMap;
#[derive(Debug, Default, Clone)]
pub struct GachaProgress {
/// The pity (counting how many pulls) of this pull (in previous record) or the next pull (in status).
pub pity: u32,
/// The failure times of this category.
pub categories_progress_map: HashMap<String, u32>,
/// The selected priority (category) for a Chooseable category.
pub categories_chosen_guarantee_category_map: HashMap<String, String>,
/// The selectedpriority (a specified item) for a Chooseable category.
pub categories_chosen_guarantee_item_map: HashMap<String, u32>,
/// The failure times for selected priority (a specified item).
pub categories_chosen_guarantee_progress_map: HashMap<String, u32>,
}
impl GachaProgress {
pub fn from_bin(bin: GachaProgressBin) -> Self {
Self {
pity: bin.pity,
categories_progress_map: bin.categories_progress_map,
categories_chosen_guarantee_category_map: bin.categories_chosen_guarantee_category_map,
categories_chosen_guarantee_item_map: bin.categories_chosen_guarantee_item_map,
categories_chosen_guarantee_progress_map: bin.categories_chosen_guarantee_progress_map,
}
}
pub fn to_bin(&self) -> GachaProgressBin {
GachaProgressBin {
pity: self.pity,
categories_progress_map: self.categories_progress_map.clone(),
categories_chosen_guarantee_category_map: self
.categories_chosen_guarantee_category_map
.clone(),
categories_chosen_guarantee_item_map: self.categories_chosen_guarantee_item_map.clone(),
categories_chosen_guarantee_progress_map: self
.categories_chosen_guarantee_progress_map
.clone(),
}
}
}
#[derive(Debug, Default, Clone)]
pub struct GachaStatus {
pub rarity_status_map: HashMap<u32, GachaProgress>,
pub discount_usage_map: HashMap<String, u32>,
}
impl GachaStatus {
pub fn from_bin(bin: GachaStatusBin) -> Self {
Self {
rarity_status_map: bin
.rarity_status_map
.into_iter()
.map(|(k, v)| (k, GachaProgress::from_bin(v)))
.collect(),
discount_usage_map: bin.discount_usage_map,
}
}
pub fn to_bin(&self) -> GachaStatusBin {
GachaStatusBin {
rarity_status_map: self
.rarity_status_map
.iter()
.map(|(k, v)| (k.clone(), v.to_bin()))
.collect(),
discount_usage_map: self.discount_usage_map.clone(),
}
}
}

View file

@ -20,7 +20,7 @@ impl Default for BasicDataModel {
nick_name: None, nick_name: None,
frontend_avatar_id: None, frontend_avatar_id: None,
beginner_procedure_id: Some(ProcedureConfigID::new_unchecked(1)), beginner_procedure_id: Some(ProcedureConfigID::new_unchecked(1)),
..Default::default() selected_post_girl_id: None,
} }
} }
} }