From 3043d45ac9aac8a4ab3a5feb605869b3cceb9473 Mon Sep 17 00:00:00 2001 From: YYHEggEgg <53960525+YYHEggEgg@users.noreply.github.com> Date: Sun, 4 Aug 2024 19:00:34 +0800 Subject: [PATCH] implement free agent & choosing UP --- assets/GachaConfig/gacha.jsonc | 1433 +++++++++++++++++ nap_data/src/gacha/gacha_config.rs | 10 + nap_gameserver/src/handlers/gacha.rs | 428 ++++- nap_gameserver/src/handlers/mod.rs | 2 + nap_gameserver/src/logic/gacha/gacha_model.rs | 126 +- nap_proto/out/_.rs | 12 +- nap_proto/out/bin.rs | 17 +- 7 files changed, 1968 insertions(+), 60 deletions(-) diff --git a/assets/GachaConfig/gacha.jsonc b/assets/GachaConfig/gacha.jsonc index e69de29..d79abd7 100644 --- a/assets/GachaConfig/gacha.jsonc +++ b/assets/GachaConfig/gacha.jsonc @@ -0,0 +1,1433 @@ +{ + // See configuration principle at: + // ZZZ Server Emu Wish system Design Brief - https://yyheggegg.github.io/mihomo-gio-blogs/zzz-gacha-impl-en/ + // + // 本文件主要根据 CBT3 测试时可用的抽卡详情页编制,部分规则依据 1.0 实际情况进行了重制。 + // Zhu Yuan (Agent) -「直如朱丝绳」独家频段: https://webstatic.mihoyo.com/nap/event/e20230424gacha/index.html?gacha_id=8de6b658666b8862dc70e14526198f3ae02e5d&lang=zh-cn®ion=prod_cb01_cn&game_biz=nap_cn + // Zhu Yuan (W-Engine) - 「喧哗奏鸣」音擎频段: https://webstatic.mihoyo.com/nap/event/e20230424gacha/index.html?gacha_id=9ee2f7078ca9c045a1c30b9fa524f7990057e1&lang=zh-cn®ion=prod_cb01_cn&game_biz=nap_cn + // Standard Wish (Agent + W-Engine) - 「热门卡司」常驻频段: https://webstatic.mihoyo.com/nap/event/e20230424gacha/index.html?gacha_id=a41a5a30e2f7b64126e5ca80ac06af304273bc&lang=zh-cn®ion=prod_cb01_cn&game_biz=nap_cn + // Victoria Housekeeping (Bangboo) -「卓越搭档」邦布频段: https://webstatic.mihoyo.com/nap/event/e20230424gacha/index.html?gacha_id=5d921fbd92214d478c9f8d6e2e2887902734b8&lang=zh-cn®ion=prod_cb01_cn&game_biz=nap_cn + "character_gacha_pool_list": [ + { + "gacha_schedule_id": 1001001, + "gacha_parent_schedule_id": 1001, + "comment": "Persist, Standard Wish", + "gacha_type": 1, + "cost_item_id": 110, // 原装母带 + "start_time": "2024-07-04T06:00:00+08:00", + "end_time": "2099-12-31T23:59:59+08:00", + "sharing_guarantee_info_category": "Standard Wish", + "order": 1, + "gacha_items": [ + { + "rarity": 4, + "extra_items_policy_tags": [ + "S-item", + ], + "probability_model_tag": "get-S-90-AgentPool", + // S级信号的基础概率为 0.600%, + // S级代理人的基础概率为 0.300%, + // S级音擎的基础概率为 0.300%。 + "categories": { + "Standard:Agent": { + "item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER", + // 此处的顺序同时由代理人自选 Policy standard-banner-300-S + // 使用,更改会导致客户端行为与服务端不一致。 + "item_ids": [ + 1181, // 格莉丝 + 1211, // 丽娜 + 1101, // 珂蕾妲 + 1021, // 猫又 + 1041, // 「11号」 + 1141, // 莱卡恩 + ], + "category_weight": 50 + }, + "Standard:W-Engine": { + "item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON", + "item_ids": [ + 14102, // 钢铁肉垫 + 14110, // 燃狱齿轮 + 14114, // 拘缚者 + 14104, // 硫磺石 + 14118, // 嵌合编译器 + 14121, // 啜泣摇篮 + ], + "category_weight": 50 + } + } + }, + { + // A级信号的基础概率为 9.400%, + // A级代理人的基础概率为 4.700%, + // A级音擎的基础概率为 4.700%。 + "rarity": 3, + "extra_items_policy_tags": [ + "A-item", + ], + "probability_model_tag": "get-A-10-AgentPool", + "categories": { + "Standard:Agent": { + "item_type": "GACHA_ADDED_ITEM_TYPE_CHARACTER", + "item_ids": [ + 1011, // 安比 + 1031, // 妮可 + 1061, // 可琳 + 1081, // 比利 + 1111, // 安东 + 1121, // 本 + 1131, // 苍角 + 1151, // 露西 + 1281, // 派派 + 1271, // 赛斯 + ], + "category_weight": 50 + }, + "Standard:W-Engine": { + "item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON", + "item_ids": [ + 13001, // 街头巨星 + 13002, // 时光切片 + 13003, // 雨林饕客 + 13004, // 星徽引擎 + 13005, // 人为刀俎 + 13006, // 贵重骨核 + 13007, // 正版变身器 + 13008, // 双生泣星 + 13010, // 兔能环 + 13101, // 德玛拉电池Ⅱ型 + 13103, // 聚宝箱 + 13106, // 家政员 + 13108, // 仿制星徽引擎 + 13111, // 旋钻机-赤轴 + 13112, // 比格气缸 + 13113, // 含羞恶面 + 13115, // 好斗的阿炮 + 13128, // 轰鸣座驾 + 13127, // 维序者-特化型 + ], + "category_weight": 50 + } + } + }, + { + "rarity": 2, + "extra_items_policy_tags": [ + "B-item", + ], + "probability_model_tag": "get-B", + "categories": { + "Standard:W-Engine": { + "item_type": "GACHA_ADDED_ITEM_TYPE_WEAPON", + "item_ids": [ + 12001, // 「月相」-望 + 12002, // 「月相」-晦 + 12003, // 「月相」-朔 + 12004, // 「残响」-Ⅰ型 + 12005, // 「残响」-Ⅱ型 + 12006, // 「残响」-Ⅲ型 + 12007, // 「湍流」-铳型 + 12008, // 「湍流」-矢型 + 12009, // 「湍流」-斧型 + 12010, // 「电磁暴」-壹式 + 12011, // 「电磁暴」-贰式 + 12012, // 「电磁暴」-叁式 + 12013, // 「恒等式」-本格 + 12014, // 「恒等式」-变格 + ], + "category_weight": 50 + } + } + } + ], + "discount_policy_tags": [ + "5x-10-poll-discount-8", + "50-poll-S", + "first-S-Agent", + "standard-banner-300-S" + ] + }, + { + "gacha_schedule_id": 5001001, + "gacha_parent_schedule_id": 5001, + "comment": "1.0, Full, Persistent, Bangboo", + "gacha_type": 5, + "cost_item_id": 112, // 邦布券 + "start_time": "2024-07-04T06:00:00+08:00", + "end_time": "2077-12-31T23:59:59+08:00", + "sharing_guarantee_info_category": "Bangboo Wish", + "gacha_items": [ + { + "rarity": 4, + "extra_items_policy_tags": [ + "S-Bangboo", + ], + "probability_model_tag": "get-S-80-WEngine", + "category_guarantee_policy_tags": [ + "chooseable-up-bangboo" + ], + "categories": { + "Standard:Bangboo": { + "item_ids": [ + 54002, // 阿全 + 54005, // 艾米莉安 + 54006, // 飚速布 + 54008, // 插头布 + 54009, // 共鸣布 + 54001, // 鲨牙布 + 54013, // 左轮布 + ], + "category_weight": 1 + }, + "Event-Exclusive:Bangboo": { + "item_ids": [ + 54004, // 巴特勒 + ], + "is_promotional_items": true, + "category_weight": 1 + } + } + }, + { + "rarity": 3, + "extra_items_policy_tags": [ + "A-Bangboo", + ], + "probability_model_tag": "get-A-10-WEngine", + "category_guarantee_policy_tags": [ + "common-up-bangboo" + ], + "categories": { + "Standard:Bangboo": { + "item_ids": [ + 53001, // 企鹅布 + 53003, // 寻宝布 + 53005, // 纸壳布 + 53004, // 扑击布 + 53006, // 纸袋布 + 53007, // 泪眼布 + 53008, // 果核布 + 53009, // 飞靶布 + 53010, // 电击布 + 53011, // 磁力布 + 54003, // 恶魔布 + ], + "category_weight": 1 + } + } + }, + { + "rarity": 2, + "extra_items_policy_tags": [ + "B-Bangboo", + ], + "probability_model_tag": "get-B", + "categories": { + "Standard:W-Engine": { + "item_ids": [ + 12001, // 「月相」-望 + 12002, // 「月相」-晦 + 12003, // 「月相」-朔 + 12004, // 「残响」-Ⅰ型 + 12005, // 「残响」-Ⅱ型 + 12006, // 「残响」-Ⅲ型 + 12007, // 「湍流」-铳型 + 12008, // 「湍流」-矢型 + 12009, // 「湍流」-斧型 + 12010, // 「电磁暴」-壹式 + 12011, // 「电磁暴」-贰式 + 12012, // 「电磁暴」-叁式 + 12013, // 「恒等式」-本格 + 12014, // 「恒等式」-变格 + ], + "category_weight": 100 + } + } + } + ] + }, + { + "gacha_schedule_id": 2001001, + "gacha_parent_schedule_id": 2001, + "comment": "1.0, First Half, Allen / 艾莲, Agent", + "gacha_type": 2, + "cost_item_id": 111, + "start_time": "2024-07-04T06:00:00+08:00", + "end_time": "2024-07-24T11:59:59+08:00", + "sharing_guarantee_info_category": "Character Event Wish", + "gacha_items": [ + { + "rarity": 4, + "extra_items_policy_tags": [ + "S-item", + ], + "probability_model_tag": "get-S-90-AgentPool", + "category_guarantee_policy_tags": [ + "promotional-items" + ], + "categories": { + "Standard:Agent": { + "item_ids": [ + 1021, // 猫又 + 1101, // 珂蕾妲 + 1041, // 「11号」 + 1141, // 莱卡恩 + 1181, // 格莉丝 + 1211, // 丽娜 + ], + "category_weight": 50 + }, + "Event-Exclusive:Agent": { + "is_promotional_items": true, + "item_ids": [ + 1191, // 艾莲 + ], + "category_weight": 50 + } + } + }, + { + // 在本期独家频段中,调频获取A级信号的基础概率为 9.400%, + // 其中A级代理人的基础概率为 7.050%, + // A级音擎的基础概率为 2.350%。 + "rarity": 3, + "extra_items_policy_tags": [ + "A-item", + ], + "probability_model_tag": "get-A-10-AgentPool", + "category_guarantee_policy_tags": [ + "promotional-items" + ], + "categories": { + "Standard:Agent": { + "item_ids": [ + 1011, // 安比 + 1031, // 妮可 + 1061, // 可琳 + 1081, // 比利 + 1121, // 本 + 1151, // 露西 + 1281, // 派派 + ], + "category_weight": 3525 + }, + "Event-Exclusive:Agent": { + "is_promotional_items": true, + "item_ids": [ + 1111, // 安东 + 1131, // 苍角 + ], + "category_weight": 3525 + }, + "Standard:W-Engine": { + "item_ids": [ + 13001, // 街头巨星 + 13002, // 时光切片 + 13003, // 雨林饕客 + 13004, // 星徽引擎 + 13005, // 人为刀俎 + 13006, // 贵重骨核 + 13007, // 正版变身器 + 13008, // 双生泣星 + 13010, // 兔能环 + 13101, // 德玛拉电池Ⅱ型 + 13103, // 聚宝箱 + 13106, // 家政员 + 13108, // 仿制星徽引擎 + 13111, // 旋钻机-赤轴 + 13112, // 比格气缸 + 13113, // 含羞恶面 + 13115, // 好斗的阿炮 + 13128, // 轰鸣座驾 + ], + "category_weight": 2350 + } + } + }, + { + "rarity": 2, + "extra_items_policy_tags": [ + "B-item", + ], + "probability_model_tag": "get-B", + "categories": { + "Standard:W-Engine": { + "item_ids": [ + 12001, // 「月相」-望 + 12002, // 「月相」-晦 + 12003, // 「月相」-朔 + 12004, // 「残响」-Ⅰ型 + 12005, // 「残响」-Ⅱ型 + 12006, // 「残响」-Ⅲ型 + 12007, // 「湍流」-铳型 + 12008, // 「湍流」-矢型 + 12009, // 「湍流」-斧型 + 12010, // 「电磁暴」-壹式 + 12011, // 「电磁暴」-贰式 + 12012, // 「电磁暴」-叁式 + 12013, // 「恒等式」-本格 + 12014, // 「恒等式」-变格 + ], + "category_weight": 100 + } + } + } + ] + }, + { + "gacha_schedule_id": 3001001, + "gacha_parent_schedule_id": 3001, + "comment": "1.0, First Half, Allen / 艾莲, W-Engine", + "gacha_type": 3, + "cost_item_id": 111, + "start_time": "2024-07-04T06:00:00+08:00", + "end_time": "2024-07-24T11:59:59+08:00", + "sharing_guarantee_info_category": "W-Engine Wish", + "gacha_items": [ + { + // 在本期音擎频段中,调频获取S级音擎的基础概率为 1.000%, + // 最多 80 次调频必能通过保底获取S级音擎。 + // S级音擎的综合概率(含保底)为 2.000%。 + "rarity": 4, + "extra_items_policy_tags": [ + "S-item", + ], + "probability_model_tag": "get-S-80-WEngine", + "category_guarantee_policy_tags": [ + "promotional-items" + ], + "categories": { + "Standard:W-Engine": { + "item_ids": [ + 14102, // 钢铁肉垫 + 14110, // 燃狱齿轮 + 14114, // 拘缚者 + 14104, // 硫磺石 + 14118, // 嵌合编译器 + 14121, // 啜泣摇篮 + ], + "category_weight": 25 + }, + "Event-Exclusive:W-Engine": { + "item_ids": [ + 14119, // 深海访客 + ], + "is_promotional_items": true, + "category_weight": 75 + } + } + }, + { + // 在本期音擎频段中,调频获取A级信号的基础概率为 15.000%, + // 其中A级音擎的基础概率为 13.125%, + // A级代理人的基础概率为 1.875%。 + // A级信号的综合概率(含保底)为 18.000%。 + // 当调频获取到A级信号时,有 75.000% 的概率为本期A级概率提升音擎中的一个。 + "rarity": 3, + "extra_items_policy_tags": [ + "A-item", + ], + "probability_model_tag": "get-A-10-WEngine", + "category_guarantee_policy_tags": [ + "promotional-items" + ], + "categories": { + "Standard:Agent": { + "item_ids": [ + 1011, // 安比 + 1031, // 妮可 + 1061, // 可琳 + 1081, // 比利 + 1111, // 安东 + 1121, // 本 + 1131, // 苍角 + 1151, // 露西 + 1281, // 派派 + ], + "category_weight": 3750 + }, + "Standard:W-Engine": { + "item_ids": [ + 13001, // 街头巨星 + 13002, // 时光切片 + 13003, // 雨林饕客 + 13004, // 星徽引擎 + 13005, // 人为刀俎 + 13006, // 贵重骨核 + 13007, // 正版变身器 + 13008, // 双生泣星 + 13010, // 兔能环 + 13101, // 德玛拉电池Ⅱ型 + 13103, // 聚宝箱 + 13106, // 家政员 + 13108, // 仿制星徽引擎 + 13112, // 比格气缸 + 13115, // 好斗的阿炮 + 13128, // 轰鸣座驾 + ], + "category_weight": 13125 + }, + "Event-Exclusive:W-Engine": { + "item_ids": [ + 13113, // 含羞恶面 + 13111, // 旋钻机-赤轴 + ], + "is_promotional_items": true, + "category_weight": 13125 + } + } + }, + { + "rarity": 2, + "extra_items_policy_tags": [ + "B-item", + ], + "probability_model_tag": "get-B", + "categories": { + "Standard:W-Engine": { + "item_ids": [ + 12001, // 「月相」-望 + 12002, // 「月相」-晦 + 12003, // 「月相」-朔 + 12004, // 「残响」-Ⅰ型 + 12005, // 「残响」-Ⅱ型 + 12006, // 「残响」-Ⅲ型 + 12007, // 「湍流」-铳型 + 12008, // 「湍流」-矢型 + 12009, // 「湍流」-斧型 + 12010, // 「电磁暴」-壹式 + 12011, // 「电磁暴」-贰式 + 12012, // 「电磁暴」-叁式 + 12013, // 「恒等式」-本格 + 12014, // 「恒等式」-变格 + ], + "category_weight": 100 + } + } + } + ] + }, + { + "gacha_schedule_id": 2001002, + "gacha_parent_schedule_id": 2002, + "comment": "1.0, Second Half, Zhu Yuan / 朱鸢, Agent", + "gacha_type": 2, + "cost_item_id": 111, + "start_time": "2024-07-24T12:00:00+08:00", + "end_time": "2024-08-01T12:00:00+08:00", + "sharing_guarantee_info_category": "Character Event Wish", + "gacha_items": [ + { + "rarity": 4, + "extra_items_policy_tags": [ + "S-item", + ], + "probability_model_tag": "get-S-90-AgentPool", + "category_guarantee_policy_tags": [ + "promotional-items" + ], + "categories": { + "Standard:Agent": { + "item_ids": [ + 1021, // 猫又 + 1101, // 珂蕾妲 + 1041, // 「11号」 + 1141, // 莱卡恩 + 1181, // 格莉丝 + 1211, // 丽娜 + ], + "category_weight": 50 + }, + "Event-Exclusive:Agent": { + "is_promotional_items": true, + "item_ids": [ + 1241 // 朱鸢 + ], + "category_weight": 50 + } + } + }, + { + "rarity": 3, + "extra_items_policy_tags": [ + "A-item", + ], + "probability_model_tag": "get-A-10-AgentPool", + "category_guarantee_policy_tags": [ + "promotional-items" + ], + "categories": { + "Standard:Agent": { + "item_ids": [ + 1011, // 安比 + 1061, // 可琳 + 1081, // 比利 + 1111, // 安东 + 1131, // 苍角 + 1151, // 露西 + 1281, // 派派 + ], + "category_weight": 3525 + }, + "Event-Exclusive:Agent": { + "is_promotional_items": true, + "item_ids": [ + 1031, // 妮可 + 1121, // 本 + ], + "category_weight": 3525 + }, + "Standard:W-Engine": { + "item_ids": [ + 13001, // 街头巨星 + 13002, // 时光切片 + 13003, // 雨林饕客 + 13004, // 星徽引擎 + 13005, // 人为刀俎 + 13006, // 贵重骨核 + 13007, // 正版变身器 + 13008, // 双生泣星 + 13010, // 兔能环 + 13101, // 德玛拉电池Ⅱ型 + 13103, // 聚宝箱 + 13106, // 家政员 + 13108, // 仿制星徽引擎 + 13111, // 旋钻机-赤轴 + 13112, // 比格气缸 + 13113, // 含羞恶面 + 13115, // 好斗的阿炮 + 13128, // 轰鸣座驾 + ], + "category_weight": 2350 + } + } + }, + { + "rarity": 2, + "extra_items_policy_tags": [ + "B-item", + ], + "probability_model_tag": "get-B", + "categories": { + "Standard:W-Engine": { + "item_ids": [ + 12001, // 「月相」-望 + 12002, // 「月相」-晦 + 12003, // 「月相」-朔 + 12004, // 「残响」-Ⅰ型 + 12005, // 「残响」-Ⅱ型 + 12006, // 「残响」-Ⅲ型 + 12007, // 「湍流」-铳型 + 12008, // 「湍流」-矢型 + 12009, // 「湍流」-斧型 + 12010, // 「电磁暴」-壹式 + 12011, // 「电磁暴」-贰式 + 12012, // 「电磁暴」-叁式 + 12013, // 「恒等式」-本格 + 12014, // 「恒等式」-变格 + ], + "category_weight": 100 + } + } + } + ] + }, + { + "gacha_schedule_id": 3001002, + "gacha_parent_schedule_id": 3002, + "comment": "1.0, Second Half, Zhu Yuan / 朱鸢, W-Engine", + "gacha_type": 3, + "cost_item_id": 111, + "start_time": "2024-07-24T12:00:00+08:00", + "end_time": "2024-08-01T12:00:00+08:00", + "sharing_guarantee_info_category": "W-Engine Wish", + "gacha_items": [ + { + "rarity": 4, + "extra_items_policy_tags": [ + "S-item", + ], + "probability_model_tag": "get-S-80-WEngine", + "category_guarantee_policy_tags": [ + "promotional-items" + ], + "categories": { + "Standard:W-Engine": { + "item_ids": [ + 14102, // 钢铁肉垫 + 14110, // 燃狱齿轮 + 14114, // 拘缚者 + 14104, // 硫磺石 + 14118, // 嵌合编译器 + 14121, // 啜泣摇篮 + ], + "category_weight": 25 + }, + "Event-Exclusive:W-Engine": { + "item_ids": [ + 14124 // 防暴者VI型 + ], + "is_promotional_items": true, + "category_weight": 75 + } + } + }, + { + "rarity": 3, + "extra_items_policy_tags": [ + "A-item", + ], + "probability_model_tag": "get-A-10-WEngine", + "category_guarantee_policy_tags": [ + "promotional-items" + ], + "categories": { + "Standard:Agent": { + "item_ids": [ + 1011, // 安比 + 1031, // 妮可 + 1061, // 可琳 + 1081, // 比利 + 1111, // 安东 + 1121, // 本 + 1131, // 苍角 + 1151, // 露西 + 1281, // 派派 + ], + "category_weight": 3750 + }, + "Standard:W-Engine": { + "item_ids": [ + 13007, // 正版变身器 + 13002, // 时光切片 + 13003, // 雨林饕客 + 13004, // 星徽引擎 + 13005, // 人为刀俎 + 13006, // 贵重骨核 + 13001, // 街头巨星 + 13008, // 双生泣星 + 13010, // 兔能环 + 13101, // 德玛拉电池Ⅱ型 + 13106, // 家政员 + 13108, // 仿制星徽引擎 + 13111, // 旋钻机-赤轴 + 13113, // 含羞恶面 + 13115, // 好斗的阿炮 + 13128, // 轰鸣座驾 + ], + "category_weight": 13125 + }, + "Event-Exclusive:W-Engine": { + "item_ids": [ + 13103, // 聚宝箱 + 13112, // 比格气缸 + ], + "is_promotional_items": true, + "category_weight": 13125 + } + } + }, + { + "rarity": 2, + "extra_items_policy_tags": [ + "B-item", + ], + "probability_model_tag": "get-B", + "categories": { + "Standard:W-Engine": { + "item_ids": [ + 12001, // 「月相」-望 + 12002, // 「月相」-晦 + 12003, // 「月相」-朔 + 12004, // 「残响」-Ⅰ型 + 12005, // 「残响」-Ⅱ型 + 12006, // 「残响」-Ⅲ型 + 12007, // 「湍流」-铳型 + 12008, // 「湍流」-矢型 + 12009, // 「湍流」-斧型 + 12010, // 「电磁暴」-壹式 + 12011, // 「电磁暴」-贰式 + 12012, // 「电磁暴」-叁式 + 12013, // 「恒等式」-本格 + 12014, // 「恒等式」-变格 + ], + "category_weight": 100 + } + } + } + ] + }, + { + "gacha_schedule_id": 2001003, + "gacha_parent_schedule_id": 2003, + "comment": "1.1, Unknown Half, QingYi / 青衣, Agent", + "gacha_type": 2, + "cost_item_id": 111, + "start_time": "2024-07-04T06:00:00+08:00", + "end_time": "2077-12-31T23:59:59+08:00", + "sharing_guarantee_info_category": "Character Event Wish", + "gacha_items": [ + { + // 在本期独家频段中,调频获取S级代理人的基础概率为 0.600%, + // 当调频获取到S级代理人时,有 50.000% 的概率为本期限定S级代理人。 + "rarity": 4, + "extra_items_policy_tags": [ + "S-item", + ], + "probability_model_tag": "get-S-90-AgentPool", + "category_guarantee_policy_tags": [ + "promotional-items" + ], + "categories": { + "Standard:Agent": { + "item_ids": [ + 1021, // 猫又 + 1101, // 珂蕾妲 + 1041, // 「11号」 + 1141, // 莱卡恩 + 1181, // 格莉丝 + 1211, // 丽娜 + ], + "category_weight": 50 + }, + "Event-Exclusive:Agent": { + "is_promotional_items": true, + "item_ids": [ + 1251, // 青衣 + ], + "category_weight": 50 + } + } + }, + { + "rarity": 3, + "extra_items_policy_tags": [ + "A-item", + ], + "probability_model_tag": "get-A-10-AgentPool", + "category_guarantee_policy_tags": [ + "promotional-items" + ], + "categories": { + "Standard:Agent": { + "item_ids": [ + 1011, // 安比 + 1031, // 妮可 + 1061, // 可琳 + 1081, // 比利 + 1111, // 安东 + 1121, // 本 + 1151, // 露西 + 1281, // 派派 + ], + "category_weight": 3525 + }, + "Event-Exclusive:Agent": { + "is_promotional_items": true, + "item_ids": [ + 1131, // 苍角 + 1271, // 赛斯 + ], + "category_weight": 3525 + }, + "Standard:W-Engine": { + "item_ids": [ + 13001, // 街头巨星 + 13002, // 时光切片 + 13003, // 雨林饕客 + 13004, // 星徽引擎 + 13005, // 人为刀俎 + 13006, // 贵重骨核 + 13007, // 正版变身器 + 13008, // 双生泣星 + 13010, // 兔能环 + 13101, // 德玛拉电池Ⅱ型 + 13103, // 聚宝箱 + 13106, // 家政员 + 13108, // 仿制星徽引擎 + 13111, // 旋钻机-赤轴 + 13112, // 比格气缸 + 13113, // 含羞恶面 + 13115, // 好斗的阿炮 + 13128, // 轰鸣座驾 + 13127, // 维序者-特化型 + ], + "category_weight": 2350 + } + } + }, + { + "rarity": 2, + "extra_items_policy_tags": [ + "B-item", + ], + "probability_model_tag": "get-B", + "categories": { + "Standard:W-Engine": { + "item_ids": [ + 12001, // 「月相」-望 + 12002, // 「月相」-晦 + 12003, // 「月相」-朔 + 12004, // 「残响」-Ⅰ型 + 12005, // 「残响」-Ⅱ型 + 12006, // 「残响」-Ⅲ型 + 12007, // 「湍流」-铳型 + 12008, // 「湍流」-矢型 + 12009, // 「湍流」-斧型 + 12010, // 「电磁暴」-壹式 + 12011, // 「电磁暴」-贰式 + 12012, // 「电磁暴」-叁式 + 12013, // 「恒等式」-本格 + 12014, // 「恒等式」-变格 + ], + "category_weight": 100 + } + } + } + ] + }, + { + "gacha_schedule_id": 3001003, + "gacha_parent_schedule_id": 3003, + "comment": "1.1, Unknown Half, QingYi / 青衣, W-Engine", + "gacha_type": 3, + "cost_item_id": 111, + "start_time": "2024-07-04T06:00:00+08:00", + "end_time": "2077-12-31T23:59:59+08:00", + "sharing_guarantee_info_category": "W-Engine Wish", + "gacha_items": [ + { + "rarity": 4, + "extra_items_policy_tags": [ + "S-item", + ], + "probability_model_tag": "get-S-80-WEngine", + "category_guarantee_policy_tags": [ + "promotional-items" + ], + "categories": { + "Standard:W-Engine": { + "item_ids": [ + 14102, // 钢铁肉垫 + 14110, // 燃狱齿轮 + 14114, // 拘缚者 + 14104, // 硫磺石 + 14118, // 嵌合编译器 + 14121, // 啜泣摇篮 + ], + "category_weight": 25 + }, + "Event-Exclusive:W-Engine": { + "item_ids": [ + 14125, // 玉壶青冰 + ], + "is_promotional_items": true, + "category_weight": 75 + } + } + }, + { + "rarity": 3, + "extra_items_policy_tags": [ + "A-item", + ], + "probability_model_tag": "get-A-10-WEngine", + "category_guarantee_policy_tags": [ + "promotional-items" + ], + "categories": { + "Standard:Agent": { + "item_ids": [ + 1011, // 安比 + 1031, // 妮可 + 1061, // 可琳 + 1081, // 比利 + 1111, // 安东 + 1121, // 本 + 1131, // 苍角 + ], + "category_weight": 3750 + }, + "Standard:W-Engine": { + "item_ids": [ + 13001, // 街头巨星 + 13002, // 时光切片 + 13003, // 雨林饕客 + 13004, // 星徽引擎 + 13005, // 人为刀俎 + 13006, // 贵重骨核 + 13007, // 正版变身器 + 13008, // 双生泣星 + 13010, // 兔能环 + 13101, // 德玛拉电池Ⅱ型 + 13103, // 聚宝箱 + 13106, // 家政员 + 13108, // 仿制星徽引擎 + 13111, // 旋钻机-赤轴 + 13112, // 比格气缸 + 13115, // 好斗的阿炮 + 13128, // 轰鸣座驾 + ], + "category_weight": 13125 + }, + "Event-Exclusive:W-Engine": { + "item_ids": [ + 13113, // 含羞恶面 + 13127, // 维序者-特化型 + ], + "is_promotional_items": true, + "category_weight": 13125 + } + } + }, + { + "rarity": 2, + "extra_items_policy_tags": [ + "B-item", + ], + "probability_model_tag": "get-B", + "categories": { + "Standard:W-Engine": { + "item_ids": [ + 12001, // 「月相」-望 + 12002, // 「月相」-晦 + 12003, // 「月相」-朔 + 12004, // 「残响」-Ⅰ型 + 12005, // 「残响」-Ⅱ型 + 12006, // 「残响」-Ⅲ型 + 12007, // 「湍流」-铳型 + 12008, // 「湍流」-矢型 + 12009, // 「湍流」-斧型 + 12010, // 「电磁暴」-壹式 + 12011, // 「电磁暴」-贰式 + 12012, // 「电磁暴」-叁式 + 12013, // 「恒等式」-本格 + 12014, // 「恒等式」-变格 + ], + "category_weight": 100 + } + } + } + ] + }, + { + "gacha_schedule_id": 2001004, + "gacha_parent_schedule_id": 2004, + "comment": "1.1, Unknown Half, Jane Doe / 简·杜, Agent", + "gacha_type": 2, + "cost_item_id": 111, + "start_time": "2024-07-04T06:00:00+08:00", + "end_time": "2077-12-31T23:59:59+08:00", + "sharing_guarantee_info_category": "Character Event Wish", + "gacha_items": [ + { + "rarity": 4, + "extra_items_policy_tags": [ + "S-item", + ], + "probability_model_tag": "get-S-90-AgentPool", + "category_guarantee_policy_tags": [ + "promotional-items" + ], + "categories": { + "Standard:Agent": { + "item_ids": [ + 1021, // 猫又 + 1101, // 珂蕾妲 + 1041, // 「11号」 + 1141, // 莱卡恩 + 1181, // 格莉丝 + 1211, // 丽娜 + ], + "category_weight": 50 + }, + "Event-Exclusive:Agent": { + "is_promotional_items": true, + "item_ids": [ + 1261, // 简 + ], + "category_weight": 50 + } + } + }, + { + "rarity": 3, + "extra_items_policy_tags": [ + "A-item", + ], + "probability_model_tag": "get-A-10-AgentPool", + "category_guarantee_policy_tags": [ + "promotional-items" + ], + "categories": { + "Standard:Agent": { + "item_ids": [ + 1011, // 安比 + 1031, // 妮可 + 1061, // 可琳 + 1081, // 比利 + 1111, // 安东 + 1121, // 本 + 1151, // 露西 + 1281, // 派派 + ], + "category_weight": 3525 + }, + "Event-Exclusive:Agent": { + "is_promotional_items": true, + "item_ids": [ + 1271, // 赛斯 + 1131, // 苍角 + ], + "category_weight": 3525 + }, + "Standard:W-Engine": { + "item_ids": [ + 13001, // 街头巨星 + 13002, // 时光切片 + 13003, // 雨林饕客 + 13004, // 星徽引擎 + 13005, // 人为刀俎 + 13006, // 贵重骨核 + 13007, // 正版变身器 + 13008, // 双生泣星 + 13010, // 兔能环 + 13101, // 德玛拉电池Ⅱ型 + 13103, // 聚宝箱 + 13106, // 家政员 + 13108, // 仿制星徽引擎 + 13111, // 旋钻机-赤轴 + 13112, // 比格气缸 + 13113, // 含羞恶面 + 13115, // 好斗的阿炮 + 13128, // 轰鸣座驾 + 13127, // 维序者-特化型 + ], + "category_weight": 2350 + } + } + }, + { + "rarity": 2, + "extra_items_policy_tags": [ + "B-item", + ], + "probability_model_tag": "get-B", + "categories": { + "Standard:W-Engine": { + "item_ids": [ + 12001, // 「月相」-望 + 12002, // 「月相」-晦 + 12003, // 「月相」-朔 + 12004, // 「残响」-Ⅰ型 + 12005, // 「残响」-Ⅱ型 + 12006, // 「残响」-Ⅲ型 + 12007, // 「湍流」-铳型 + 12008, // 「湍流」-矢型 + 12009, // 「湍流」-斧型 + 12010, // 「电磁暴」-壹式 + 12011, // 「电磁暴」-贰式 + 12012, // 「电磁暴」-叁式 + 12013, // 「恒等式」-本格 + 12014, // 「恒等式」-变格 + ], + "category_weight": 100 + } + } + } + ] + }, + { + "gacha_schedule_id": 3001004, + "gacha_parent_schedule_id": 3004, + "comment": "1.1, Unknown Half, Jane Doe / 简·杜, W-Engine", + "gacha_type": 3, + "cost_item_id": 111, + "start_time": "2024-07-04T06:00:00+08:00", + "end_time": "2077-12-31T23:59:59+08:00", + "sharing_guarantee_info_category": "W-Engine Wish", + "gacha_items": [ + { + "rarity": 4, + "extra_items_policy_tags": [ + "S-item", + ], + "probability_model_tag": "get-S-80-WEngine", + "category_guarantee_policy_tags": [ + "promotional-items" + ], + "categories": { + "Standard:W-Engine": { + "item_ids": [ + 14102, // 钢铁肉垫 + 14110, // 燃狱齿轮 + 14114, // 拘缚者 + 14104, // 硫磺石 + 14118, // 嵌合编译器 + 14121, // 啜泣摇篮 + ], + "category_weight": 25 + }, + "Event-Exclusive:W-Engine": { + "item_ids": [ + 14126, // 淬锋钳刺 + ], + "is_promotional_items": true, + "category_weight": 75 + } + } + }, + { + "rarity": 3, + "extra_items_policy_tags": [ + "A-item", + ], + "probability_model_tag": "get-A-10-WEngine", + "category_guarantee_policy_tags": [ + "promotional-items" + ], + "categories": { + "Standard:Agent": { + "item_ids": [ + 1011, // 安比 + 1031, // 妮可 + 1061, // 可琳 + 1081, // 比利 + 1111, // 安东 + 1121, // 本 + 1131, // 苍角 + ], + "category_weight": 3750 + }, + "Standard:W-Engine": { + "item_ids": [ + 13001, // 街头巨星 + 13002, // 时光切片 + 13003, // 雨林饕客 + 13004, // 星徽引擎 + 13005, // 人为刀俎 + 13006, // 贵重骨核 + 13007, // 正版变身器 + 13008, // 双生泣星 + 13010, // 兔能环 + 13101, // 德玛拉电池Ⅱ型 + 13103, // 聚宝箱 + 13106, // 家政员 + 13108, // 仿制星徽引擎 + 13111, // 旋钻机-赤轴 + 13112, // 比格气缸 + 13115, // 好斗的阿炮 + 13128, // 轰鸣座驾 + ], + "category_weight": 13125 + }, + "Event-Exclusive:W-Engine": { + "item_ids": [ + 13127, // 维序者-特化型 + 13113, // 含羞恶面 + ], + "is_promotional_items": true, + "category_weight": 13125 + } + } + }, + { + "rarity": 2, + "extra_items_policy_tags": [ + "B-item", + ], + "probability_model_tag": "get-B", + "categories": { + "Standard:W-Engine": { + "item_ids": [ + 12001, // 「月相」-望 + 12002, // 「月相」-晦 + 12003, // 「月相」-朔 + 12004, // 「残响」-Ⅰ型 + 12005, // 「残响」-Ⅱ型 + 12006, // 「残响」-Ⅲ型 + 12007, // 「湍流」-铳型 + 12008, // 「湍流」-矢型 + 12009, // 「湍流」-斧型 + 12010, // 「电磁暴」-壹式 + 12011, // 「电磁暴」-贰式 + 12012, // 「电磁暴」-叁式 + 12013, // 「恒等式」-本格 + 12014, // 「恒等式」-变格 + ], + "category_weight": 100 + } + } + } + ] + }, + ], + "probability_model_map": { + // 以下内容根据 @一棵平衡树 公测后解析进行了更新。 + // https://www.bilibili.com/video/BV1oW42197RR + "get-S-90-AgentPool": { + "points": [ + { + "start_pity": 1, + "start_chance_percent": 0.6 + }, + { + "start_pity": 73, + "start_chance_percent": 0.6, + "increment_percent": 6 + } + ] + }, + "get-A-10-AgentPool": { + "clear_status_on_higher_rarity_pulled": true, + "points": [ + { + "start_pity": 1, + "start_chance_percent": 9.4 + }, + { + "start_pity": 10, + "start_chance_percent": 100 + } + ] + }, + "get-B": { + "points": [ + { + "start_pity": 1, + "start_chance_percent": 100 + } + ] + }, + "get-S-80-WEngine": { + // "maximum_guarantee_pity": 80, + "points": [ + { + "start_pity": 1, + "start_chance_percent": 1 + }, + { + "start_pity": 64, + "start_chance_percent": 1, + "increment_percent": 6 + }, + { + "start_pity": 80, + "start_chance_percent": 100 + } + ] + }, + "get-A-10-WEngine": { + "clear_status_on_higher_rarity_pulled": true, + "points": [ + { + "start_pity": 1, + "start_chance_percent": 15 + }, + { + "start_pity": 10, + "start_chance_percent": 100 + } + ] + } + }, + "extra_items_policy_map": { + "S-item": { + "id": 115, // 信号余波 + "count": 40 + }, + "A-item": { + "id": 115, // 信号余波 + "count": 8 + }, + "B-item": { + "id": 117, // 信号残响 + "count": 20 + }, + "S-Bangboo": { + "id": 116, // 邦布币 + "count": 50 + }, + "A-Bangboo": { + "id": 116, // 邦布币 + "count": 10 + }, + "B-Bangboo": { + "id": 116, // 邦布币 + "count": 2 + } + }, + "discount_policies": { + "ten_pull_discount_map": { + "5x-10-poll-discount-8": { + "use_limit": 5, + "discounted_prize": 8 + }, + }, + "must_gain_item_map": { + "first-S-Agent": { + "use_limit": 1, + "rarity": 4, + "category_tag": "Standard:Agent" + }, + }, + "advanced_guarantee_map": { + "50-poll-S": { + "use_limit": 1, + "rarity": 4, + "guarantee_pity": 50 + }, + }, + "free_select_map": { + "standard-banner-300-S": { + "free_select_progress_record_tag": "standard-banner-300-S__free_select_total_progress", + "free_select_usage_record_tag": "standard-banner-300-S__free_select_milestone_idx", + "rarity": 4, + "category_tags": [ + "Standard:Agent", + ], + "milestones": [ + 300, + ] + }, + } + }, + "category_guarantee_policy_map": { + "promotional-items": { + "included_category_tags": [ + // 当调频获取到S级代理人时,有 50.000% 的概率为本期限定S级代理人。 + "Event-Exclusive:Agent", + "Event-Exclusive:W-Engine" + ], + "trigger_on_failure_times": 1, + "clear_status_on_target_changed": false, + "chooseable": false + }, + "chooseable-up-bangboo": { + "included_category_tags": [ + "Standard:Bangboo", + "Event-Exclusive:Bangboo" + ], + "trigger_on_failure_times": 0, + "clear_status_on_target_changed": false, + "chooseable": true + }, + "common-up-bangboo": { + "included_category_tags": [ + "Standard:Bangboo", + "Event-Exclusive:Bangboo" + ], + "trigger_on_failure_times": 0, + "clear_status_on_target_changed": false, + "chooseable": false + } + }, + // 定义供祈愿显示信息使用的额外属性。 + "common_properties": { + "up_item_category_tag": "promotional-items", + "s_item_rarity": 4, + "a_item_rarity": 3, + "ten_pull_discount_tag": "5x-10-poll-discount-8", + "newcomer_advanced_s_tag": "50-poll-S" + } +} \ No newline at end of file diff --git a/nap_data/src/gacha/gacha_config.rs b/nap_data/src/gacha/gacha_config.rs index c539ac3..486319f 100644 --- a/nap_data/src/gacha/gacha_config.rs +++ b/nap_data/src/gacha/gacha_config.rs @@ -126,11 +126,21 @@ pub struct MustGainItem { pub category_tag: String, } +#[derive(Debug, Default, Deserialize)] +pub struct FreeSelectItem { + pub milestones: Vec, + pub rarity: u32, + pub category_tags: Vec, + pub free_select_progress_record_tag: String, + pub free_select_usage_record_tag: String, +} + #[derive(Debug, Default, Deserialize)] pub struct DiscountPolicyCollection { pub ten_pull_discount_map: HashMap, pub must_gain_item_map: HashMap, pub advanced_guarantee_map: HashMap, + pub free_select_map: HashMap, } impl DiscountPolicyCollection { diff --git a/nap_gameserver/src/handlers/gacha.rs b/nap_gameserver/src/handlers/gacha.rs index 31fd93a..20c0163 100644 --- a/nap_gameserver/src/handlers/gacha.rs +++ b/nap_gameserver/src/handlers/gacha.rs @@ -1,3 +1,11 @@ +use std::{ + cmp::min, + collections::{ + hash_map::Entry::{Occupied, Vacant}, + HashSet, + }, +}; + use data::{ gacha::{gacha_config::*, global_gacha_config}, tables::{AvatarBaseID, WeaponID}, @@ -24,7 +32,7 @@ pub async fn on_get_gacha_data( Ok(GetGachaDataScRsp { retcode: Retcode::RetSucc.into(), gacha_type: req.gacha_type, - gacha_data: Some(generate_all_gacha_info(_player)), + gacha_data: Some(generate_all_gacha_info(_player, &Local::now())), }) } } @@ -45,8 +53,12 @@ pub async fn on_do_gacha( &pull_time, ); 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::RetSucc.into(), + retcode: Retcode::RetFail.into(), ..Default::default() }); }; @@ -72,11 +84,15 @@ pub async fn on_do_gacha( 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; + // cost_count = discount_policy.discounted_prize; } } } if cost_count != req.cost_item_count { + tracing::info!( + "refuse gacha because: expected cost item {cost_count}, found {}", + req.cost_item_count + ); return Ok(DoGachaScRsp { retcode: Retcode::RetFail.into(), ..Default::default() @@ -88,7 +104,7 @@ pub async fn on_do_gacha( let mut gain_item_list: Vec = 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(); + let extra_item_bin = pull_result.extra_item_bin.clone().unwrap(); let uid = match GachaAddedItemType::try_from(pull_result.item_type) { Ok(enum_val) => match enum_val { GachaAddedItemType::Weapon => match WeaponID::new(pull_result.obtained_item_id) { @@ -123,16 +139,236 @@ pub async fn on_do_gacha( ..GainItemInfo::default() }); pull_count -= 1; + gacha_model.gacha_bin.gacha_records.push(pull_result); } Ok(DoGachaScRsp { retcode: Retcode::RetSucc.into(), gain_item_list, - gacha_data: Some(generate_all_gacha_info(_player)), + gacha_data: Some(generate_all_gacha_info(_player, &pull_time)), cost_item_count: req.cost_item_count, }) } +pub async fn on_gacha_free_agent( + _session: &NetSession, + _player: &mut Player, + req: GachaFreeAgentCsReq, +) -> NetResult { + let gachaconf = global_gacha_config(); + let gacha_model = &mut _player.gacha_model; + let role_model = &mut _player.role_model; + let pull_time = Local::now(); + let target_pool = get_gacha_pool( + &gachaconf.character_gacha_pool_list, + &req.gacha_parent_schedule_id, + &pull_time, + ); + if let None = target_pool { + tracing::info!( + "refuse free agent because: pool of parent_schedule_id {} not found", + req.gacha_parent_schedule_id + ); + return Ok(GachaFreeAgentScRsp { + retcode: Retcode::RetFail.into(), + ..Default::default() + }); + }; + let target_pool = target_pool.unwrap(); + let gacha_bin = &mut _player.gacha_model.gacha_bin; + let sharing_guarantee_category_tag = &target_pool.sharing_guarantee_info_category; + let status_bin = gacha_bin + .gacha_status_map + .get_mut(sharing_guarantee_category_tag) + .unwrap(); + + let mut free_select_policy: Option<&FreeSelectItem> = None; + let mut free_select_progress: u32 = 0; + let mut free_select_required_pull: u32 = 0; + for discount_policy_tag in target_pool.discount_policy_tags.iter() { + if gachaconf + .discount_policies + .free_select_map + .contains_key(discount_policy_tag) + { + let policy = gachaconf + .discount_policies + .free_select_map + .get(discount_policy_tag) + .unwrap(); + let free_select_demand_idx = usize::try_from( + *(status_bin + .discount_usage_map + .get(&policy.free_select_usage_record_tag) + .unwrap()), + ) + .unwrap(); + if policy.milestones.len() <= free_select_demand_idx { + continue; + } + + let free_select_actual_progress = status_bin + .discount_usage_map + .get(&policy.free_select_progress_record_tag) + .unwrap(); + free_select_policy = Some(policy); + free_select_required_pull = policy + .milestones + .get(free_select_demand_idx) + .unwrap() + .to_owned(); + free_select_progress = min(free_select_required_pull, *free_select_actual_progress); + } + } + + if let None = free_select_policy { + tracing::info!( + "refuse free agent because: pool of parent_schedule_id {} hasn't defined free agent discount yet (or used up chance)", + req.gacha_parent_schedule_id + ); + return Ok(GachaFreeAgentScRsp { + retcode: Retcode::RetFail.into(), + ..Default::default() + }); + } else if free_select_progress < free_select_required_pull { + tracing::info!( + "refuse free agent because: use pulled {free_select_progress} (after last free agent) in parent_schedule_id {}, required {free_select_required_pull}", + req.gacha_parent_schedule_id + ); + return Ok(GachaFreeAgentScRsp { + retcode: Retcode::RetFail.into(), + ..Default::default() + }); + } + let free_select_policy = free_select_policy.unwrap(); + let mut has_demanded_item = false; + for rarity_items in target_pool.gacha_items.iter() { + if rarity_items.rarity != free_select_policy.rarity { + continue; + } + for (category_tag, category) in rarity_items.categories.iter() { + if !free_select_policy.category_tags.contains(category_tag) { + continue; + } + has_demanded_item |= category.item_ids.contains(&req.avatar_id); + } + } + if !has_demanded_item { + tracing::info!( + "refuse free agent because: pool of parent_schedule_id {} doesn't have demanded item {}", + req.gacha_parent_schedule_id, req.avatar_id + ); + return Ok(GachaFreeAgentScRsp { + retcode: Retcode::RetFail.into(), + ..Default::default() + }); + } + + role_model.add_avatar(AvatarBaseID::new(req.avatar_id).unwrap()); + (*status_bin + .discount_usage_map + .get_mut(&free_select_policy.free_select_usage_record_tag) + .unwrap()) += 1; + (*status_bin + .discount_usage_map + .get_mut(&free_select_policy.free_select_progress_record_tag) + .unwrap()) -= free_select_required_pull; + + Ok(GachaFreeAgentScRsp { + retcode: Retcode::RetSucc.into(), + }) +} + +pub async fn on_choose_gacha_up( + _session: &NetSession, + _player: &mut Player, + req: ChooseGachaUpCsReq, +) -> NetResult { + let gachaconf = global_gacha_config(); + let gacha_model = &mut _player.gacha_model; + let pull_time = Local::now(); + let target_pool = get_gacha_pool( + &gachaconf.character_gacha_pool_list, + &req.gacha_parent_schedule_id, + &pull_time, + ); + if let None = target_pool { + return Ok(ChooseGachaUpScRsp { + retcode: Retcode::RetFail.into(), + ..Default::default() + }); + }; + let target_pool = target_pool.unwrap(); + + for rarity_items in target_pool.gacha_items.iter() { + if rarity_items.rarity != gachaconf.common_properties.s_item_rarity { + continue; + } + for guarantee_policy_tag in rarity_items.category_guarantee_policy_tags.iter() { + let category_guarantee_policy = gachaconf + .category_guarantee_policy_map + .get(guarantee_policy_tag) + .unwrap(); + if !category_guarantee_policy.chooseable { + continue; + } + let mut up_category: Option<&String> = None; + for (category_tag, category) in rarity_items.categories.iter() { + if category.item_ids.contains(&req.item_id) { + up_category = Some(category_tag); + break; + } + } + if let None = up_category { + return Ok(ChooseGachaUpScRsp { + retcode: Retcode::RetFail.into(), + ..Default::default() + }); + }; + let up_category = up_category.unwrap(); + + let progress_bin = gacha_model + .gacha_bin + .gacha_status_map + .get_mut(&target_pool.sharing_guarantee_info_category) + .unwrap() + .rarity_status_map + .get_mut(&rarity_items.rarity) + .unwrap(); + match progress_bin + .categories_chosen_guarantee_item_map + .entry(guarantee_policy_tag.clone()) + { + Occupied(mut occupied_entry) => { + occupied_entry.insert(req.item_id); + } + Vacant(vacant_entry) => { + vacant_entry.insert(req.item_id); + } + }; + match progress_bin + .categories_chosen_guarantee_category_map + .entry(up_category.clone()) + { + Occupied(mut occupied_entry) => { + occupied_entry.insert(up_category.clone()); + } + Vacant(vacant_entry) => { + vacant_entry.insert(up_category.clone()); + } + }; + return Ok(ChooseGachaUpScRsp { + retcode: Retcode::RetSucc.into(), + ..Default::default() + }); + } + } + + Ok(ChooseGachaUpScRsp { + retcode: Retcode::RetFail.into(), + ..Default::default() + }) +} fn generate_gacha_info_from_pool( gacha_bin: &GachaModelBin, target_pool: &CharacterGachaPool, @@ -154,34 +390,13 @@ fn generate_gacha_info_from_pool( .get(&common_properties.a_item_rarity) .unwrap() .pity; - let mut up_s_item_list: Vec = vec![]; - let mut up_a_item_list: Vec = 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; + 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 @@ -211,6 +426,123 @@ fn generate_gacha_info_from_pool( discount_ten_roll_prize = policy.discounted_prize; discount_avaliable_num = policy.use_limit - discount_usage; } + } else if gachaconf + .discount_policies + .free_select_map + .contains_key(discount_policy_tag) + { + let policy = gachaconf + .discount_policies + .free_select_map + .get(discount_policy_tag) + .unwrap(); + let free_select_demand_idx = usize::try_from( + *(status_bin + .discount_usage_map + .get(&policy.free_select_usage_record_tag) + .unwrap()), + ) + .unwrap(); + if policy.milestones.len() <= free_select_demand_idx { + continue; + } + + let free_select_actual_progress = status_bin + .discount_usage_map + .get(&policy.free_select_progress_record_tag) + .unwrap(); + free_select_policy = Some(policy); + free_select_required_pull = policy + .milestones + .get(free_select_demand_idx) + .unwrap() + .to_owned(); + free_select_progress = min(free_select_required_pull, *free_select_actual_progress); + } + } + + let mut up_s_item_list: Vec = vec![]; + let mut up_a_item_list: Vec = vec![]; + let mut free_select_item_list: Vec = vec![]; + let mut chooseable_up_list: Vec = vec![]; + let mut chosen_up_item: u32 = 0; + let mut s_guarantee: u32 = 0; + let mut a_guarantee: u32 = 0; + for rarity_items in target_pool.gacha_items.iter() { + let mut chooseable_up_included_category_tags: Option<&HashSet> = None; + let mut chooseable_policy_tag: Option<&String> = None; + for guarantee_policy_tag in rarity_items.category_guarantee_policy_tags.iter() { + let category_guarantee_policy = gachaconf + .category_guarantee_policy_map + .get(guarantee_policy_tag) + .unwrap(); + if !category_guarantee_policy.chooseable { + continue; + } + chooseable_policy_tag = Some(guarantee_policy_tag); + chooseable_up_included_category_tags = + Some(&category_guarantee_policy.included_category_tags); + if let Some(item) = status_bin + .rarity_status_map + .get(&rarity_items.rarity) + .unwrap() + .categories_chosen_guarantee_item_map + .get(guarantee_policy_tag) + { + chosen_up_item = item.clone(); + } + } + + for (category_tag, category) in rarity_items.categories.iter() { + let probability_model = gachaconf + .probability_model_map + .get(&rarity_items.probability_model_tag) + .unwrap(); + let maximum_pity = &probability_model.maximum_guarantee_pity; + if rarity_items.rarity == common_properties.s_item_rarity { + if category.is_promotional_items { + up_s_item_list = category.item_ids.clone(); + } + // tracing::info!("pity_s: {pity_s}"); + // thread 'tokio-runtime-worker' panicked at nap_gameserver\src\handlers\gacha.rs:369:31: + // attempt to subtract with overflow + s_guarantee = maximum_pity - min(pity_s, maximum_pity.clone()) + 1; + } + if rarity_items.rarity == common_properties.a_item_rarity { + if category.is_promotional_items { + up_a_item_list = category.item_ids.clone(); + } + // tracing::info!("pity_a: {pity_a}"); + a_guarantee = maximum_pity - min(pity_a, maximum_pity.clone()) + 1; + } + + if let Some(val) = free_select_policy { + if val.rarity == rarity_items.rarity && val.category_tags.contains(category_tag) { + free_select_item_list.append(&mut category.item_ids.clone()); + } + } + + if let Some(tags) = chooseable_up_included_category_tags { + if tags.contains(category_tag) { + chooseable_up_list.append(&mut category.item_ids.clone()); + } + } + } + + if let Some(priority_policy_tag) = chooseable_policy_tag { + // if let Some(item) = status_bin + // .rarity_status_map + // .get(&rarity_items.rarity) + // .unwrap() + // .categories_chosen_guarantee_item_map + // .get(priority_policy_tag) + // { + if rarity_items.rarity == gachaconf.common_properties.s_item_rarity { + up_s_item_list = vec![]; + } else if rarity_items.rarity == gachaconf.common_properties.a_item_rarity { + up_a_item_list = vec![]; + } + // } } } @@ -219,7 +551,7 @@ fn generate_gacha_info_from_pool( need_item_count: 1, }]; - Gacha { + 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, @@ -227,34 +559,46 @@ fn generate_gacha_info_from_pool( 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, - lpklhoobkbh: target_pool.gacha_parent_schedule_id, - nammdglepbk: 593, - hgmcofcjmbg: 101, + 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) -> GachaData { +fn generate_all_gacha_info(_player: &Player, now: &DateTime) -> GachaData { let gachaconf = global_gacha_config(); let gacha_bin = &_player.gacha_model.gacha_bin; let mut gacha_list: Vec = 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, - )); + 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: 0, + random_number: 6167, gacha_pool: Some(GachaPool { gacha_list }), ..GachaData::default() } diff --git a/nap_gameserver/src/handlers/mod.rs b/nap_gameserver/src/handlers/mod.rs index 4e83d49..c869ef7 100644 --- a/nap_gameserver/src/handlers/mod.rs +++ b/nap_gameserver/src/handlers/mod.rs @@ -120,6 +120,8 @@ req_handlers! { quest::BeginArchiveBattleQuest; quest::FinishArchiveQuest; gacha::DoGacha; + gacha::ChooseGachaUp; + gacha::GachaFreeAgent; } notify_handlers! { diff --git a/nap_gameserver/src/logic/gacha/gacha_model.rs b/nap_gameserver/src/logic/gacha/gacha_model.rs index 5f70037..ba201db 100644 --- a/nap_gameserver/src/logic/gacha/gacha_model.rs +++ b/nap_gameserver/src/logic/gacha/gacha_model.rs @@ -52,10 +52,42 @@ impl GachaModel { &mut progress_bin.categories_progress_map, &category_guarantee_policy_tag, ); + + let guarantee_policy = gachaconf + .category_guarantee_policy_map + .get(category_guarantee_policy_tag) + .unwrap(); + if !guarantee_policy.chooseable { + continue; + } + get_or_add( + &mut progress_bin.categories_chosen_guarantee_progress_map, + &category_guarantee_policy_tag, + ); } } for discount_policy_tag in gacha_pool.discount_policy_tags.iter() { - get_or_add(&mut status_bin.discount_usage_map, &discount_policy_tag); + if gachaconf + .discount_policies + .free_select_map + .contains_key(discount_policy_tag) + { + let policy = gachaconf + .discount_policies + .free_select_map + .get(discount_policy_tag) + .unwrap(); + get_or_add( + &mut status_bin.discount_usage_map, + &policy.free_select_progress_record_tag, + ); + get_or_add( + &mut status_bin.discount_usage_map, + &policy.free_select_usage_record_tag, + ); + } else { + get_or_add(&mut status_bin.discount_usage_map, &discount_policy_tag); + } } } self @@ -70,8 +102,14 @@ impl GachaModel { 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); + let result = determine_gacha_result( + pull_time, + category, + target_pool, + status_bin, + progress_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); @@ -196,9 +234,27 @@ fn determine_category<'bin, 'conf>( let mut category_tag_inited = false; let mut category_tag_result: HashSet = 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 + // 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() { @@ -262,14 +318,49 @@ fn determine_gacha_result<'bin, 'conf>( category: &'conf GachaCategoryInfo, target_pool: &'conf CharacterGachaPool, status_bin: &'bin GachaStatusBin, + progress_bin: &'bin GachaProgressBin, 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 item_id: Option<&u32> = None; + // We should see whether user's search priority exists. + 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; + } + // Firstly, judge whether the user failed enough times. + // The user is limited to get only this category's item, + // so we should record the user's failure to get his + // selected item elsewhere. + if progress_bin + .categories_chosen_guarantee_progress_map + .get(guarantee_policy_tag) + .unwrap() + < &category_guarantee_policy.trigger_on_failure_times + { + continue; + } + // We directly look whether user chose an UP item. + if let Some(item) = progress_bin + .categories_chosen_guarantee_item_map + .get(guarantee_policy_tag) + { + item_id = Some(item); + } + } + + let item_id = match item_id { + Some(val) => val, + None => category + .item_ids + .get(rand::thread_rng().gen_range(0..item_pool_len) as usize) + .unwrap(), + }; let mut extra_item_id: u32 = 0; let mut extra_item_count: u32 = 0; @@ -279,6 +370,7 @@ fn determine_gacha_result<'bin, 'conf>( .get(extra_items_policy_tag) .unwrap(); // TODO: apply_on_owned_count in a context with bag + // TODO: That's what RoleModel should do, not me. if extra_items_policy.apply_on_owned_count == 0 { extra_item_id = extra_items_policy.id; extra_item_count = extra_items_policy.count; @@ -396,4 +488,18 @@ fn update_discount<'bin, 'conf>( *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; + } } diff --git a/nap_proto/out/_.rs b/nap_proto/out/_.rs index 447fe85..7bc0341 100644 --- a/nap_proto/out/_.rs +++ b/nap_proto/out/_.rs @@ -6844,7 +6844,7 @@ pub struct Pofhbffcjap { #[derive(proto_gen::XorFields)] #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] -pub struct Nhffnnompfh { +pub struct ChooseGachaUpScRsp { #[xor(4109)] #[prost(int32, tag = "2")] pub retcode: i32, @@ -17992,7 +17992,7 @@ pub mod gbcnocdacgf { #[prost(message, tag = "12")] Summonee(super::Endadpkgkid), #[prost(message, tag = "13")] - BuddyId(super::Ecjcmfjjgdp), + Buddy(super::Ecjcmfjjgdp), #[prost(message, tag = "14")] DropItem(super::Mfbjkggafmo), #[prost(message, tag = "15")] @@ -22295,7 +22295,7 @@ pub struct Gacha { pub gacha_info_list_webview: ::prost::alloc::string::String, #[xor(9009)] #[prost(uint32, tag = "419")] - pub ekjlhhdekka: u32, + pub chosen_up_item: u32, #[prost(string, tag = "923")] pub fjohnbicmce: ::prost::alloc::string::String, #[xor(1379)] @@ -24558,7 +24558,7 @@ pub struct Obpccjhnbpe { #[derive(proto_gen::XorFields)] #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] -pub struct Pahjnbjogon { +pub struct ChooseGachaUpCsReq { #[xor(3180)] #[prost(uint32, tag = "6")] pub item_id: u32, @@ -27531,7 +27531,7 @@ pub struct TipsInfo { #[derive(proto_gen::XorFields)] #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] -pub struct Ieimfkpmegp { +pub struct GachaFreeAgentCsReq { #[xor(378)] #[prost(uint32, tag = "12")] pub gacha_parent_schedule_id: u32, @@ -31687,7 +31687,7 @@ pub struct Amhlhmjgcpk { #[derive(proto_gen::XorFields)] #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] -pub struct Idonpiailid { +pub struct GachaFreeAgentScRsp { #[xor(3883)] #[prost(int32, tag = "3")] pub retcode: i32, diff --git a/nap_proto/out/bin.rs b/nap_proto/out/bin.rs index 01bb4aa..f4ea087 100644 --- a/nap_proto/out/bin.rs +++ b/nap_proto/out/bin.rs @@ -117,6 +117,7 @@ pub struct MainCityModelBin { #[prost(uint32, tag = "3")] pub section_id: u32, } +/// The progress record of a specified rarity. All maps' keys are category_guarantee_policy_tag. #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)] pub struct GachaProgressBin { @@ -129,12 +130,24 @@ pub struct GachaProgressBin { ::prost::alloc::string::String, u32, >, - /// The selected priority for a Chooseable category. + /// The selected priority (category) for a Chooseable category. #[prost(map = "string, string", tag = "3")] - pub category_chosen_guarantee_map: ::std::collections::HashMap< + pub categories_chosen_guarantee_category_map: ::std::collections::HashMap< ::prost::alloc::string::String, ::prost::alloc::string::String, >, + /// The selectedpriority (a specified item) for a Chooseable category. + #[prost(map = "string, uint32", tag = "4")] + pub categories_chosen_guarantee_item_map: ::std::collections::HashMap< + ::prost::alloc::string::String, + u32, + >, + /// The failure times for selected priority (a specified item). + #[prost(map = "string, uint32", tag = "5")] + pub categories_chosen_guarantee_progress_map: ::std::collections::HashMap< + ::prost::alloc::string::String, + u32, + >, } #[allow(clippy::derive_partial_eq_without_eq)] #[derive(Clone, PartialEq, ::prost::Message)]