You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
240 lines
10 KiB
240 lines
10 KiB
|
3 weeks ago
|
# 需求:背包刷新
|
||
|
|
|
||
|
|
> 状态:**已落地核心实现**(含连败组合字保底)
|
||
|
|
> 日期:2026-09-16
|
||
|
|
> 关联:[需求-新项目概述.md](./需求-新项目概述.md)、[开发规范.md](./开发规范.md)
|
||
|
|
|
||
|
|
## 1. 目标与范围
|
||
|
|
|
||
|
|
| 项 | 说明 |
|
||
|
|
|----|------|
|
||
|
|
| 目标 | 玩家局内背包:全新刷新算法 + 复用老交互/动画;组合仅横向 |
|
||
|
|
| 对象 | **仅玩家背包** |
|
||
|
|
| 槽位 | 固定 **5** |
|
||
|
|
| 刷新入口 | **仅普通刷新**(扣桃);**不做广告刷新** |
|
||
|
|
| 字池 | `Text.type` ∈ `{1, 2, 4}`(基础字 / 组合字 / 铲子) |
|
||
|
|
| 本期不做 | 段位升降玩法、type=3 入池、老项目 P2·P3·P4 保底;**AI 字池/背包**见 [需求-AI决策.md](./需求-AI决策.md)(复用加权抽字,不含 §4.3 连败保底) |
|
||
|
|
|
||
|
|
### 1.1 与老项目关系
|
||
|
|
|
||
|
|
| 维度 | 策略 |
|
||
|
|
|------|------|
|
||
|
|
| 刷新算法 | **全新实现**;禁止参考或沿用老 `BagWeaponPool` / P2·P3·P4 保底 / 章节解锁权重 / 地图降权等 |
|
||
|
|
| 拖动与放置 | 行为与老项目一致,实现时可复用现有视图与 Merge 落子 |
|
||
|
|
| 刷新/放置动画 | 表现与老项目一致,实现时可复用现有动画类 |
|
||
|
|
| 文字组合 | 读 `Hero.text`;**仅横向**,禁止竖向 |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 2. 存档前置:当前段位 id
|
||
|
|
|
||
|
|
刷新算法依赖玩家 **当前段位 id** 查 `Rank.charactersweight`。
|
||
|
|
|
||
|
|
| 项 | 约定 |
|
||
|
|
|----|------|
|
||
|
|
| 存档字段 | 须持有当前段位 id(现有 `GameDoc.rankId`,默认 `1`;另有 `rankStars` 本期不消费) |
|
||
|
|
| 读档 | 缺失或非法时修正为合法默认(如 `1`),方向对齐现有 `ConfigProjectData.repairRankOnDocLoad` |
|
||
|
|
| 读取入口 | 实现阶段经统一入口读取(如 `RankDataManager.getPlayerRankId()`) |
|
||
|
|
| 局内 | 开战时用存档 `rankId` 作为本局选档依据;**整局不变** |
|
||
|
|
| 段位模块 | **升星/掉星/匹配等后续实现**;本期只保证字段可读写,不写段位玩法规则 |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 3. 配表契约
|
||
|
|
|
||
|
|
### 3.1 Text(字池)
|
||
|
|
|
||
|
|
路径:[`assets/bundles/first/config/Text.json`](../assets/bundles/first/config/Text.json)
|
||
|
|
|
||
|
|
| 字段 | 含义 |
|
||
|
|
|------|------|
|
||
|
|
| `id` | 文字 id |
|
||
|
|
| `icon` | 展示用字/资源名 |
|
||
|
|
| `type` | `1` 基础字 / `2` 武将组合字 / `3` buff(**不入池**)/ `4` 铲子 |
|
||
|
|
| `quality` | 品质:`0` 白 / `1` 绿 / `2` 蓝 / `3` 紫 / `4` 橙(AI 空位取舍见 [需求-AI决策.md](./需求-AI决策.md) §6.4) |
|
||
|
|
| `weight` | 基础权重 |
|
||
|
|
| `number1` ~ `number20` | 对应档位下该字在局内池中的 **最大数量** |
|
||
|
|
|
||
|
|
### 3.2 Rank(段位)
|
||
|
|
|
||
|
|
路径:[`assets/bundles/first/config/Rank.json`](../assets/bundles/first/config/Rank.json)
|
||
|
|
|
||
|
|
| 字段 | 含义 |
|
||
|
|
|------|------|
|
||
|
|
| `id` | 段位 id(与存档 `rankId` 对应) |
|
||
|
|
| `charactersweight` | 本局选档数组:元素为整数 `N`,表示使用 `Text.numberN`;玩家读己方段位,AI 读对手段位(算法相同) |
|
||
|
|
|
||
|
|
说明:
|
||
|
|
|
||
|
|
- `N` **不是** Text.id,也不是直接权重值。
|
||
|
|
- 样例:低段 `[1]`;中段滑动窗口如 `[4,5,6]`;高段如 `[9,10]`。
|
||
|
|
- 玩家与 AI **共用本字段算法**;AI 建池时传入对手段位,见 [需求-AI决策.md](./需求-AI决策.md)。
|
||
|
|
|
||
|
|
### 3.3 Hero(组合)
|
||
|
|
|
||
|
|
路径:[`assets/bundles/first/config/Hero.json`](../assets/bundles/first/config/Hero.json)
|
||
|
|
|
||
|
|
| 字段 | 含义 |
|
||
|
|
|------|------|
|
||
|
|
| `text` | 组成文字的 Text id **有序列表** |
|
||
|
|
|
||
|
|
- 单字英雄:如 `[1]`。
|
||
|
|
- 组合神将:如悟空 `[11,12]`;顺序参与匹配,`[11,12]` ≠ `[12,11]`。
|
||
|
|
- 概述中的 `characters` 命名以配表 **`text`** 为准。
|
||
|
|
|
||
|
|
### 3.4 Const(刷新消耗)
|
||
|
|
|
||
|
|
路径:[`assets/bundles/first/config/Const.json`](../assets/bundles/first/config/Const.json)
|
||
|
|
|
||
|
|
| id | 含义 |
|
||
|
|
|----|------|
|
||
|
|
| 4 | 战斗初始刷新消耗(桃子) |
|
||
|
|
| 5 | 战斗刷新消耗递增值 |
|
||
|
|
| 30 | 连败组合字保底:`para1` 万分比(现 2500=25%),概率 = 连败次数 × 该值;胜利后连败清零 |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 4. 刷新算法
|
||
|
|
|
||
|
|
### 4.1 流程
|
||
|
|
|
||
|
|
```mermaid
|
||
|
|
flowchart TD
|
||
|
|
startBattle[开战读存档rankId] --> pickN["从 Rank.charactersweight 等概率随机一个 N"]
|
||
|
|
pickN --> initPool["每字 remaining = Text.numberN"]
|
||
|
|
initPool --> onRefresh[每次刷新填满5槽]
|
||
|
|
onRefresh --> calcW["最终权重 = weight * remaining"]
|
||
|
|
calcW --> pick[加权随机选一字]
|
||
|
|
pick --> dec["该字 remaining -= 1"]
|
||
|
|
dec --> more{槽未满?}
|
||
|
|
more -->|是| onRefresh
|
||
|
|
more -->|否| done[本轮结束]
|
||
|
|
```
|
||
|
|
|
||
|
|
### 4.2 规则细则
|
||
|
|
|
||
|
|
1. **开局确定档位 N(整局一次)**
|
||
|
|
- 用存档 `rankId` 取 `Rank` 行。
|
||
|
|
- 从 `charactersweight` **等概率**随机一个整数 `N`。
|
||
|
|
- 本局所有刷新共用该 `N`,**不再重 roll**。
|
||
|
|
|
||
|
|
2. **初始化局内池(整局一次)**
|
||
|
|
- 候选范围:`Text.type ∈ {1,2,4}`,且 `weight > 0`,且对应 `numberN > 0`。
|
||
|
|
- 对每个候选字:`remaining[id] = Text.numberN`。
|
||
|
|
- **不含** `type = 3`。
|
||
|
|
|
||
|
|
3. **最终权重**
|
||
|
|
- `最终权重 = weight × remaining`(`remaining` 为 **当前剩余数量**)。
|
||
|
|
- `remaining == 0` 的字不进入本次抽取候选。
|
||
|
|
|
||
|
|
4. **抽取与扣减**
|
||
|
|
- 每次普通刷新:向背包 **5** 个槽逐槽加权随机填字(或等价地连续抽满 5 次)。
|
||
|
|
- 每抽出一字:该字 `remaining -= 1`。
|
||
|
|
- 扣减 **整局累计**,跨多次刷新 **不重置**。
|
||
|
|
- **首次刷新**必须至少 1 个可攻击单字(落下即普攻:刀/枪/棍/弓;不含营、铲、桃、组合碎字)。否则开局打不到来敌、没桃、无法再刷,卡死。池里没有任何攻击字时 `warn`,不瞎填。
|
||
|
|
|
||
|
|
5. **异常**
|
||
|
|
- 某次抽槽时候选全空(或权重总和 ≤ 0):须 `warn` 暴露问题并按失败处理。
|
||
|
|
- **禁止**静默用默认字/随便挑一个掩盖错误(见开发规范)。
|
||
|
|
|
||
|
|
6. **刷新费用**
|
||
|
|
- 普通刷新扣桃:读 Const id `4` / `5`。
|
||
|
|
- **无广告刷新**;产品与实现均不提供广告刷入口。
|
||
|
|
|
||
|
|
### 4.3 连败组合字保底(仅玩家)
|
||
|
|
|
||
|
|
连败次数复用存档 `matchLoseStreak`(`MatchStreakStats`,胜清零、跨天不清)。**AI 刷新不走本段。**
|
||
|
|
|
||
|
|
**完整组合英雄:** 玩家已持有某 `herotype=2` 英雄 `text` 中的全部字(地图散字或已激活均可)。两半都在但没拼上,也算完整英雄,不算碎字。
|
||
|
|
|
||
|
|
**碎字:** 地图上未纳入完整英雄的组合字(`Text.type=2`)+ 本次刷新已抽出的组合字。即将被刷掉的旧背包不算。
|
||
|
|
|
||
|
|
**触发:** 本局尚未出现完整组合英雄,且碎字已有 4 个时,抽出的第 5 个及之后每个组合字都做判定。保底失败则下一个组合字继续 roll,不是只判一次。
|
||
|
|
|
||
|
|
**概率:** `min(10000, 连败次数 × Const30.para1)` 万分比。连败 4 次 → 100%。连败 0 则不会命中。
|
||
|
|
|
||
|
|
**给字:** 命中后不看第 5 个字本身该配谁,从已有碎字所缺的另一半里去重后等概率随机一个(`悟` 可缺 `空/能/净`)。扣字池 remaining,归还原抽中字。候选为空或 remaining 耗尽则跳过并打日志,不瞎填。
|
||
|
|
|
||
|
|
**本局停止:** 刷新凑齐一对、或地图已有完整英雄后,本局不再判定(含之后把将铲掉)。GM 固定/预设刷新不走保底。
|
||
|
|
|
||
|
|
### 4.4 新号第一局神将字(仅玩家)
|
||
|
|
|
||
|
|
详见 [需求-首局神将字.md](./需求-首局神将字.md)。GM 固定/预设不走。AI 不走。
|
||
|
|
|
||
|
|
开战时若尚无对局痕迹:第一次普通刷新必含哪吒/杨戬/悟空六字之一;记下配对另一半;第二次必给该另一半(与是否拖上地图无关)。第一次已抽出完整一对则第二次不补。替换时不得拿掉本批唯一攻击字。
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 5. 交互与放置
|
||
|
|
|
||
|
|
行为与老项目一致(实现可复用现有拖放与 Merge):
|
||
|
|
|
||
|
|
| 能力 | 要求 |
|
||
|
|
|------|------|
|
||
|
|
| 拖动 | 拉线、ghost、落点引导、拖动时战斗画面弱化等既有表现;**不对局暂停/慢放** |
|
||
|
|
| 背包 ↔ 背包 | 空槽移动;同字可合则升级;否则互换 |
|
||
|
|
| 背包 ↔ 地图 | 落子、升级、互换;铲子开挖等既有逻辑 |
|
||
|
|
| 地图 ↔ 地图 | 与现网一致 |
|
||
|
|
| 刷新按钮 | 仅普通刷新;费用不足不可刷 |
|
||
|
|
|
||
|
|
不在本期改变放置规则本身;本期差异仅在「刷出什么字」与「组合方向」。
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 6. 文字组合
|
||
|
|
|
||
|
|
| 项 | 约定 |
|
||
|
|
|----|------|
|
||
|
|
| 配置源 | `Hero.text`(Text id 有序序列) |
|
||
|
|
| 匹配 | 地图连续格序列与 `text` **精确相等** |
|
||
|
|
| 方向 | **仅横向**(左 → 右) |
|
||
|
|
| 禁止 | **竖向组合**(上 → 下不再生效) |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 7. 动画表现
|
||
|
|
|
||
|
|
| 场景 | 要求 |
|
||
|
|
|------|------|
|
||
|
|
| 刷新入场 | 与老项目一致(如抛物线入场 + 落地 squash) |
|
||
|
|
| 放置跳入 | 与老项目一致(松手后跳入目标格/槽) |
|
||
|
|
| 验收 | 观感对齐现网;实现阶段复用现有动画组件即可 |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 8. 明确不做
|
||
|
|
|
||
|
|
- 老项目 P2·P3·P4 刷新保底 / 脸黑脸白 / 强制格子·铲 id。新产品只做 §4.3 连败组合字保底与 §4.4 新号首局神将字。
|
||
|
|
- `Text.type = 3` 进入刷新池。
|
||
|
|
- 广告刷新。
|
||
|
|
- 将 §4.3 / §4.4 保底套到 AI 背包。
|
||
|
|
- 段位升星、掉星、匹配、每日俸禄等局外段位玩法。
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 9. 验收清单
|
||
|
|
|
||
|
|
- [ ] 存档可读当前段位 id;非法时有明确修正;开战用该 id 查 Rank
|
||
|
|
- [ ] 开局从 `charactersweight` 只随机一次 `N`,整局固定
|
||
|
|
- [ ] 池初始 `remaining = numberN`;仅 type 1/2/4 且 weight>0 且 numberN>0
|
||
|
|
- [ ] 最终权重 = `weight × remaining`;抽中后 remaining 减一且整局累计
|
||
|
|
- [ ] 每次刷新填满 5 槽;无广告刷新
|
||
|
|
- [ ] 首次刷新至少 1 个可攻击单字(刀/枪/棍/弓)
|
||
|
|
- [ ] 候选耗尽有告警/失败,无静默兜底
|
||
|
|
- [ ] 组合只认 `Hero.text`,仅横向,竖向不成
|
||
|
|
- [ ] 拖放交互与刷新/放置动画与老项目表现一致
|
||
|
|
- [ ] 范围不含 AI 背包
|
||
|
|
- [ ] 连败组合字保底:4 个碎字后第 5 个及之后组合字按连败×Const30 判定;凑齐一对本局停止;仅玩家
|
||
|
|
- [ ] 新号第一局神将字:前两次刷新凑齐哪吒/杨戬/悟空之一;仅玩家;见 [需求-首局神将字.md](./需求-首局神将字.md)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 10. 实现阶段提示(非本期交付)
|
||
|
|
|
||
|
|
审查通过后的实现方向(供后续方案引用,**本文不要求改代码**):
|
||
|
|
|
||
|
|
- 新建玩家局内字池/刷新服务,替换旧权重池与保底算法调用链。
|
||
|
|
- 组合匹配去掉竖向扫描;配表桥接统一读 `Hero.text`。
|
||
|
|
- 去掉广告刷新入口与相关会话 API。
|
||
|
|
- 同步修正概述文档中 `charactersweight` / `Hero.characters` 与本文不一致处。
|