diff --git a/doc/design/修炼系统设计.md b/doc/design/修炼系统设计.md index 360998f..4282761 100644 --- a/doc/design/修炼系统设计.md +++ b/doc/design/修炼系统设计.md @@ -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 步)。 diff --git a/doc/design/弟子系统设计.md b/doc/design/弟子系统设计.md index 58c9b0f..73fa65f 100644 --- a/doc/design/弟子系统设计.md +++ b/doc/design/弟子系统设计.md @@ -218,7 +218,7 @@ aptitude_weights=[60, 30, 8, 2] |------|------| | 标题 | "弟子名录(N 人)",N = 当前名录人数 | | 列表区 | ScrollContainer 动态条目,一个弟子一行,按入宗顺序 | -| 条目内容 | 姓名 / 资质(含天灵根标记)/ 灵根 / 境界 + "详情"按钮 | +| 条目内容 | 姓名 / 资质(含天灵根标记)/ 灵根 / 境界 / **修为进度** + "详情"按钮 | | 空状态 | 名录为空时显示"暂无弟子"提示 | | 底部 | "关闭"按钮(也可点遮罩关闭) | @@ -227,7 +227,7 @@ aptitude_weights=[60, 30, 8, 2] | 元素 | 说明 | |------|------| | 标题 | 弟子姓名 + 天灵根标记 | -| 信息区 | 静态展示:名字 / 资质 / 年龄 / 灵根 / 境界 / **入宗时间** | +| 信息区 | 静态展示:名字 / 资质 / 年龄 / 灵根 / 境界 / **修为进度** / **入宗时间** | | 预留区 | 修炼进度、属性面板等 D5+ 扩展位置(本期留白) | | 底部 | "关闭"按钮 | diff --git a/project.godot b/project.godot index f67bb02..e863e97 100644 --- a/project.godot +++ b/project.godot @@ -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] diff --git a/resources/game_config.cfg b/resources/game_config.cfg index 4fecc9a..063fe46 100644 --- a/resources/game_config.cfg +++ b/resources/game_config.cfg @@ -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 字体大小 diff --git a/src/Character/CultivationManager.gd b/src/Character/CultivationManager.gd new file mode 100644 index 0000000..94e584c --- /dev/null +++ b/src/Character/CultivationManager.gd @@ -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) diff --git a/src/Character/CultivationManager.gd.uid b/src/Character/CultivationManager.gd.uid new file mode 100644 index 0000000..62697c0 --- /dev/null +++ b/src/Character/CultivationManager.gd.uid @@ -0,0 +1 @@ +uid://q4pk0n21oawr diff --git a/src/Character/Disciple.gd b/src/Character/Disciple.gd index e3f3560..6137d8b 100644 --- a/src/Character/Disciple.gd +++ b/src/Character/Disciple.gd @@ -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])] diff --git a/src/Core/GameConfig.gd b/src/Core/GameConfig.gd index a168d95..3aac044 100644 --- a/src/Core/GameConfig.gd +++ b/src/Core/GameConfig.gd @@ -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", []) diff --git a/src/UI/Panels/DisciplesPanel/DisciplesPanel.tscn b/src/UI/Panels/DisciplesPanel/DisciplesPanel.tscn index 6eb57c7..cd2b6a1 100644 --- a/src/UI/Panels/DisciplesPanel/DisciplesPanel.tscn +++ b/src/UI/Panels/DisciplesPanel/DisciplesPanel.tscn @@ -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 diff --git a/src/UI/Panels/DisciplesPanel/disciple_detail_panel.gd b/src/UI/Panels/DisciplesPanel/disciple_detail_panel.gd index 0f3516b..c679176 100644 --- a/src/UI/Panels/DisciplesPanel/disciple_detail_panel.gd +++ b/src/UI/Panels/DisciplesPanel/disciple_detail_panel.gd @@ -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 diff --git a/src/UI/Panels/DisciplesPanel/disciples_panel.gd b/src/UI/Panels/DisciplesPanel/disciples_panel.gd index 7568369..bcdb5e2 100644 --- a/src/UI/Panels/DisciplesPanel/disciples_panel.gd +++ b/src/UI/Panels/DisciplesPanel/disciples_panel.gd @@ -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))