feat: 修炼循环闭环——升层/瓶颈突破(按层概率)+管理界面修为进度+面板放大

This commit is contained in:
2026-08-08 18:16:05 +08:00
parent 19f8294242
commit 543af0a4ff
11 changed files with 142 additions and 21 deletions
+15 -13
View File
@@ -2,7 +2,7 @@
> 对应开发计划表 D5(8/7):① 修为随时间增长(资质×灵气)② 境界门槛 ③ 突破判定。
> 交付标准:弟子自动修炼,可突破。本文档只描述设计,不包含代码。
> 状态:设计稿,待审阅通过后实现
> 状态:①②③ 已实现(每日结算 → 修为达门槛升层 → 瓶颈掷骰判定,失败扣 20%;13 层圆满封顶);修为进度已在管理界面展示
---
@@ -72,7 +72,7 @@
## 四、突破判定(瓶颈层)
1. 修为 ≥ 瓶颈层门槛时,每日结算后自动尝试突破
2. 掷骰:成功概率 = 配置 `breakthrough_chance`(默认 0.8
2. 掷骰:成功概率**按瓶颈层分别配置**`breakthrough_chances`,与 `bottleneck_levels` 一一对应:升 4 层 0.85 / 升 7 层 0.8 / 升 10 层 0.75 / 升 13 层 0.6,越往上越难
3. **成功** → 升层,控制台打印"X 突破至 练气4层"
4. **失败** → 修为扣减 20%(冲关损耗),次日再尝试;控制台打印失败信息
5. 失败不连续重试(每天最多判定一次)
@@ -105,9 +105,9 @@ base_speed=10 ; 基础修为/日
qi_concentration=1.0 ; 宗门灵气浓度(D6 动态化)
aptitude_speed=[0.6, 1.0, 1.5, 2.0] ; 资质倍率(下品/中品/上品/天骄)
heavenly_bonus=1.5 ; 天灵根额外倍率
breakthrough_chance=0.8 ; 瓶颈突破成功率(待接入
level_thresholds=[100, 200, 350, 550, 800, 1100, 1500, 2000, 2600, 3300, 4100, 5000] ; 待接入
bottleneck_levels=[4, 7, 10, 13] ; 待接入
breakthrough_chances=[0.85, 0.8, 0.75, 0.6] ; 瓶颈突破成功率(与 bottleneck_levels 一一对应
level_thresholds=[100, 200, 350, 550, 800, 1100, 1500, 2000, 2600, 3300, 4100, 5000] ; 练气 1→2 到 12→13 各层所需修为
bottleneck_levels=[4, 7, 10, 13] ; 瓶颈目标层(升到这些层需突破判定)
```
- `aptitude_speed`:与资质枚举一一对应,必须 4 项
@@ -118,9 +118,9 @@ bottleneck_levels=[4, 7, 10, 13] ; 待接入
## 七、UI 接入(D4 预留区填充)
- **名录列表**DisciplesPanel):条目追加"修为"列(如 "1234 / 800",当前修为/本层门槛;13 层显示"练气圆满"
- **详情弹窗**:信息区追加"修为:1234 / 2000"(当前/本层门槛;13 层显示 练气圆满)
- **名录列表**:境界列实时变化(realm_changed 后自动刷新,现有 disciples_changed 不触发,需详情面板监听 realm_changed
- 修炼日志:控制台 print(本期不做日志界面)
- 面板打开时暂停时间,进度静态展示;修炼日志:控制台 print(本期不做日志界面
---
@@ -138,9 +138,11 @@ bottleneck_levels=[4, 7, 10, 13] ; 待接入
## 九、验收清单(D5 交付标准)
- [ ] 挂机时弟子修为逐日增长,速度随资质/天灵根不同(下品慢、天骄快)
- [ ] 非瓶颈层修为满自动升层,无失败
- [ ] 瓶颈层(4/7/10/13 层)需突破判定,控制台有成败日志,失败扣 20% 修为
- [ ] 练气 13 层圆满后修为封顶,显示"练气圆满"
- [ ] 详情面板显示修为进度,突破后实时刷新
- [ ] 修改配置(速度/概率/门槛)重启生效
- [x] 挂机时弟子修为逐日增长,速度随资质/天灵根不同(下品慢、天骄快)
- [x] 非瓶颈层修为满自动升层,无失败
- [x] 瓶颈层(4/7/10/13 层)需突破判定,控制台有成败日志,失败扣 20% 修为
- [x] 练气 13 层圆满后修为封顶,显示"练气圆满"
- [x] 详情面板显示修为进度,突破后实时刷新
- [x] 修改配置(速度/概率/门槛)重启生效
> 注:修为显示("当前/本层门槛")待详情面板接入(拆解第 6 步)。
+2 -2
View File
@@ -218,7 +218,7 @@ aptitude_weights=[60, 30, 8, 2]
|------|------|
| 标题 | "弟子名录(N 人)"N = 当前名录人数 |
| 列表区 | ScrollContainer 动态条目,一个弟子一行,按入宗顺序 |
| 条目内容 | 姓名 / 资质(含天灵根标记)/ 灵根 / 境界 + "详情"按钮 |
| 条目内容 | 姓名 / 资质(含天灵根标记)/ 灵根 / 境界 / **修为进度** + "详情"按钮 |
| 空状态 | 名录为空时显示"暂无弟子"提示 |
| 底部 | "关闭"按钮(也可点遮罩关闭) |
@@ -227,7 +227,7 @@ aptitude_weights=[60, 30, 8, 2]
| 元素 | 说明 |
|------|------|
| 标题 | 弟子姓名 + 天灵根标记 |
| 信息区 | 静态展示:名字 / 资质 / 年龄 / 灵根 / 境界 / **入宗时间** |
| 信息区 | 静态展示:名字 / 资质 / 年龄 / 灵根 / 境界 / **修为进度** / **入宗时间** |
| 预留区 | 修炼进度、属性面板等 D5+ 扩展位置(本期留白) |
| 底部 | "关闭"按钮 |
+1
View File
@@ -22,6 +22,7 @@ Settings="*res://src/Core/Settings.gd"
SectManager="*res://src/Core/SectManager.gd"
GameConfig="*res://src/Core/GameConfig.gd"
DiscipleManager="*res://src/Character/DiscipleManager.gd"
CultivationManager="*res://src/Character/CultivationManager.gd"
[display]
+4 -1
View File
@@ -6,7 +6,7 @@
difficulty="normal"
opening_applicant_count=5
opening_pick_count=3
recruit_interval_years=0.25
recruit_interval_years=12
recruit_applicant_count=3
heavenly_root_chance=0.5 ; 天灵根概率:仅天骄资质中再掷,50%
root_count_weights=[20, 20, 20, 20, 20] ; 灵根数量权重(1根/2根/3根/4根/5根,和=100 即百分比概率)
@@ -24,6 +24,9 @@ base_speed=10 ; 基础修为/日
qi_concentration=1.0 ; 宗门灵气浓度
aptitude_speed=[0.6, 1.0, 1.5, 2.0] ; 资质倍率(下品/中品/上品/天骄)
heavenly_bonus=1.5 ; 天灵根额外修炼速度倍率
breakthrough_chances=[0.85, 0.8, 0.75, 0.6] ; 各瓶颈层突破成功率(与 bottleneck_levels 一一对应:升4层/7层/10层/13层)
level_thresholds=[100, 200, 350, 550, 800, 1100, 1500, 2000, 2600, 3300, 4100, 5000] ; 练气 1→2 到 12→13 各层所需修为
bottleneck_levels=[4, 7, 10, 13] ; 瓶颈目标层(升到这些层需突破判定)
[ui] ; UI 通用
hud_font_size=28 ; HUD 字体大小
+74
View File
@@ -0,0 +1,74 @@
## 修炼管理器(Autoload 单例)。
## 负责弟子修为的每日结算与境界推进(D5:修为逐日增长 + 自动升层 + 瓶颈突破判定)。
extends Node
## 境界变化信号(突破后发出,供详情面板刷新)。
signal realm_changed(d: Disciple)
## 修炼日志文本(控制台打印,后续日志系统接入)。
signal cultivation_log(text: String)
func _ready() -> void:
# 每日推进时结算全体已入宗弟子的修为与境界
TimeSystem.day_passed.connect(_on_day_passed)
## 每日结算:仅结算已入宗名录(候选池不修炼)。
func _on_day_passed(_year: int, _month: int, _day: int) -> void:
for d in DiscipleManager.disciples:
_gain_exp(d)
_check_breakthrough(d)
## 按公式累计一日修为。
## 速度 = 基础速度 × 资质倍率 × 灵根容量 × 天灵根加成 × 灵气浓度
## 练气圆满(13 层)后修为封顶,不再增长。
## @param d: 已入宗弟子
func _gain_exp(d: Disciple) -> void:
# 练气圆满(13 层)后修为封顶,不再增长
if d.realm_index != 0 or d.sub_realm_index >= GameConfig.level_thresholds.size():
return
var speed := GameConfig.cultivation_base_speed
speed *= GameConfig.aptitude_speed[d.aptitude]
speed *= d.get_root_count_capacity()
if d.is_heavenly:
speed *= GameConfig.heavenly_bonus
speed *= GameConfig.qi_concentration
d.cultivation_exp += speed
## 突破判定:修为满当前层门槛时推进境界。
## 非瓶颈目标层自动升层;瓶颈目标层掷骰(成功率来自配置),失败扣 20% 修为。
## 每日最多推进一层。
## @param d: 已入宗弟子
func _check_breakthrough(d: Disciple) -> void:
if d.realm_index != 0:
return
var layer := d.sub_realm_index + 1
# 13 层圆满:无更高门槛,不再判定
if layer > GameConfig.level_thresholds.size():
return
if d.cultivation_exp < GameConfig.level_thresholds[layer - 1]:
return
if GameConfig.bottleneck_levels.has(layer + 1):
# 成功率按目标层从配置取(与 bottleneck_levels 一一对应)
var idx := maxi(0, GameConfig.bottleneck_levels.find(layer + 1))
if randf() < GameConfig.bottleneck_chances[idx]:
_advance_layer(d)
else:
d.cultivation_exp *= 0.8
_log("%s 冲关 %s 失败,修为损耗两成" % [d.name, d.get_realm_text()])
else:
_advance_layer(d)
## 推进一层境界:扣除本层门槛修为(余量带入下一层)并发信号。
## @param d: 已入宗弟子
func _advance_layer(d: Disciple) -> void:
var threshold := GameConfig.level_thresholds[d.sub_realm_index]
d.cultivation_exp -= threshold
d.sub_realm_index += 1
realm_changed.emit(d)
_log("%s 突破至 %s" % [d.name, d.get_realm_text()])
## 输出修炼日志(控制台打印 + 信号广播)。
## @param text: 日志文本
func _log(text: String) -> void:
cultivation_log.emit(text)
print(text)
+1
View File
@@ -0,0 +1 @@
uid://q4pk0n21oawr
+10
View File
@@ -28,6 +28,7 @@ var join_month: int = 0 # 入宗月份
var join_day: int = 0 # 入宗日
var realm_index: int = 0 # 大境界索引(0=练气)
var sub_realm_index: int = 0 # 小境界索引(练气层数-1
var cultivation_exp: float = 0.0 # 修为(float 可积累小数,D5 每日结算)
# —— 辅助函数:把数据翻译成给人看的文本 ——
func get_aptitude_name() -> String:
@@ -57,3 +58,12 @@ func get_join_date_text() -> String:
if join_year <= 0:
return ""
return "%d%d%d" % [join_year, join_month, join_day]
## 本层修炼进度文本,如 "1234 / 2000"(当前修为 / 本层门槛)。
## 练气圆满(13 层)返回"练气圆满";非练气期返回空串。
func get_cultivation_progress_text() -> String:
if realm_index != 0:
return ""
if sub_realm_index >= GameConfig.level_thresholds.size():
return "练气圆满"
return "%d / %d" % [int(cultivation_exp), int(GameConfig.level_thresholds[sub_realm_index])]
+24
View File
@@ -31,6 +31,12 @@ var qi_concentration := 1.0
var aptitude_speed: Array[float] = [0.6, 1.0, 1.5, 2.0]
## 天灵根额外修炼速度倍率。
var heavenly_bonus := 1.5
## 各瓶颈层突破成功率(与 bottleneck_levels 一一对应:升4层/7层/10层/13层)。
var bottleneck_chances: Array[float] = [0.85, 0.8, 0.75, 0.6]
## 练气期各层升级所需修为(练气 1→2 到 12→13,共 12 项)。
var level_thresholds: Array[float] = [100.0, 200.0, 350.0, 550.0, 800.0, 1100.0, 1500.0, 2000.0, 2600.0, 3300.0, 4100.0, 5000.0]
## 瓶颈目标层(升到这些层需突破判定)。
var bottleneck_levels: Array[int] = [4, 7, 10, 13]
# —— UI 通用(对应 cfg 的 [ui] 节)——
## HUD 字体大小。
@@ -69,6 +75,24 @@ func _load_config() -> void:
cultivation_base_speed = cfg.get_value("cultivation", "base_speed", cultivation_base_speed)
qi_concentration = cfg.get_value("cultivation", "qi_concentration", qi_concentration)
heavenly_bonus = cfg.get_value("cultivation", "heavenly_bonus", heavenly_bonus)
var bc_raw: Variant = cfg.get_value("cultivation", "breakthrough_chances", [])
if bc_raw is Array and bc_raw.size() == bottleneck_chances.size():
var arr_bc: Array[float] = []
for v in bc_raw:
arr_bc.append(float(v))
bottleneck_chances = arr_bc
var th_raw: Variant = cfg.get_value("cultivation", "level_thresholds", [])
if th_raw is Array and th_raw.size() == level_thresholds.size():
var arr_th: Array[float] = []
for v in th_raw:
arr_th.append(float(v))
level_thresholds = arr_th
var bn_raw: Variant = cfg.get_value("cultivation", "bottleneck_levels", [])
if bn_raw is Array and bn_raw.size() == bottleneck_levels.size():
var arr_bn: Array[int] = []
for v in bn_raw:
arr_bn.append(int(v))
bottleneck_levels = arr_bn
# UI 通用
hud_font_size = cfg.get_value("ui", "hud_font_size", hud_font_size)
var apt_raw: Variant = cfg.get_value("cultivation", "aptitude_speed", [])
@@ -66,7 +66,7 @@ theme_override_colors/font_color = Color(0.82, 0.95, 0.88, 1)
text = "弟子名录"
[node name="ScrollContainer" type="ScrollContainer" parent="CenterContainer/Panel/MarginContainer/VBoxContainer" unique_id=780000008]
custom_minimum_size = Vector2(640, 340)
custom_minimum_size = Vector2(1280, 680)
layout_mode = 2
size_flags_vertical = 3
@@ -14,12 +14,13 @@ func _ready() -> void:
## @param d: 要展示的弟子
func show_detail(d: Disciple) -> void:
title_label.text = d.name + ("(天灵根)" if d.is_heavenly else "")
info_label.text = "名字:%s\n资质:%s\n年龄:%d\n灵根:%s\n境界:%s\n入宗时间:%s" % [
info_label.text = "名字:%s\n资质:%s\n年龄:%d\n灵根:%s\n境界:%s\n修为:%s\n入宗时间:%s" % [
d.name,
d.get_aptitude_name(),
d.age,
d.get_roots_text(),
d.get_realm_text(),
d.get_cultivation_progress_text(),
d.get_join_date_text(),
]
visible = true
@@ -58,19 +58,19 @@ func _add_entry(d: Disciple) -> void:
var name_label := Label.new()
name_label.text = d.name
name_label.custom_minimum_size = Vector2(90, 0)
name_label.custom_minimum_size = Vector2(180, 0)
row.add_child(name_label)
var aptitude_label := Label.new()
aptitude_label.text = d.get_aptitude_name() + ("·天灵根" if d.is_heavenly else "")
if APTITUDE_COLORS.has(d.aptitude):
aptitude_label.add_theme_color_override("font_color", APTITUDE_COLORS[d.aptitude])
aptitude_label.custom_minimum_size = Vector2(130, 0)
aptitude_label.custom_minimum_size = Vector2(260, 0)
row.add_child(aptitude_label)
var roots_label := Label.new()
roots_label.text = d.get_roots_text() + "灵根"
roots_label.custom_minimum_size = Vector2(160, 0)
roots_label.custom_minimum_size = Vector2(320, 0)
row.add_child(roots_label)
var realm_label := Label.new()
@@ -78,6 +78,11 @@ func _add_entry(d: Disciple) -> void:
realm_label.size_flags_horizontal = Control.SIZE_EXPAND_FILL
row.add_child(realm_label)
var progress_label := Label.new()
progress_label.text = d.get_cultivation_progress_text()
progress_label.custom_minimum_size = Vector2(220, 0)
row.add_child(progress_label)
var detail_button := Button.new()
detail_button.text = "详情"
detail_button.button_up.connect(_on_detail_button_up.bind(d))