diff --git a/README.md b/README.md index b9681dcc..9fdc9a6b 100644 --- a/README.md +++ b/README.md @@ -181,6 +181,7 @@ current_scene 是通过 GroundLoader 加载的,在 ground loader 加载 ground - 影响 SignSnapper 的等待时长(如 Boss 战时加快节奏) - 转场 process 机制优化:暂停 & AnimationPlayer 保持运行 - EventManager 控制事件,使用 Event2D 控制绑定关系 + - formula_dict: 前置事件推动后续。示例(条件数>=1): `xx=2 & yy=1 & xy=3 -> zz=1` - 比 Event2D 更轻量灵活的 EventBinder,内有 updater 与 trigger 两种绑定 - updater 由 event 驱动更新父节点状态 - trigger 由父节点 signal 驱动更新 event diff --git a/asset/dialogue/event_stage.csv b/asset/dialogue/event_stage.csv new file mode 100644 index 00000000..1d119a39 --- /dev/null +++ b/asset/dialogue/event_stage.csv @@ -0,0 +1,15 @@ +keys,zh_CN,_character,_notes,_tags +c03_invite_xchan_supper=2 & c03_laizi_braid=2 ~ c03_f2_madman_runaway=2,c03_invite_xchan_supper=2 & c03_laizi_braid=2 ~ c03_f2_madman_runaway=2,,, +c03_invite_xchan_supper=2 & c03_laizi_braid=2 -> c03_f2_madman_runaway=2,c03_invite_xchan_supper=2 & c03_laizi_braid=2 -> c03_f2_madman_runaway=2,,, +0:demo 1:release,0:demo 1:release,release_stage,, +1:序章 2:第一章 3:第二章 4:第三章 5:第四章 6:尾声,1:序章 2:第一章 3:第二章 4:第三章 5:第四章 6:尾声,current_chapter_stage,, +0:初始化_关闭 1:打开 2:放入小蝉人偶 3:全部放置正确_可摇手柄 4:已播放完成,0:初始化_关闭 1:打开 2:放入小蝉人偶 3:全部放置正确_可摇手柄 4:已播放完成,c02_musicbox_stage,, +0:初始化 1:已交互疯子 2:小鞋已掉落,0:初始化 1:已交互疯子 2:小鞋已掉落,c02_madman_interacted_stage,, +0:初始化 1:寻找弹珠_老虎钳可以换弹珠 2:给出弹珠 3:游戏结束_小猫纸片 4:游戏结束_小猫离开,0:初始化 1:寻找弹珠_老虎钳可以换弹珠 2:给出弹珠 3:游戏结束_小猫纸片 4:游戏结束_小猫离开,c02_ball_game_stage,, +0:初始化 1:已放肉,0:初始化 1:已放肉,c03_s01_meat_put,, +0:初始化 1:已偷听_需邀请 2:完成邀请,0:初始化 1:已偷听_需邀请 2:完成邀请,c03_invite_xchan_supper,, +0:初始化 1:已使用剪刀 2:已剪下,0:初始化 1:已使用剪刀 2:已剪下,c03_laizi_braid,, +0:初始化 1:跑开_纸人挡路 2:消除纸人,0:初始化 1:跑开_纸人挡路 2:消除纸人,c03_f2_madman_runaway,, +c03_invite_xchan_supper=2 & c03_laizi_braid=2 & c03_mahjong_game=1 -> c03_f2_madman_runaway=2,c03_invite_xchan_supper=2 & c03_laizi_braid=2 & c03_mahjong_game=1 -> c03_f2_madman_runaway=2,,, +0:初始化 1:粘舌头和刀把 2:给药 4:准备好进入游戏,0:初始化 1:粘舌头和刀把 2:给药 4:准备好进入游戏,c03_before_mahjong_game,, +0:麻将理牌 1:麻将出千 2:麻将结束 3:演出结束,0:麻将理牌 1:麻将出千 2:麻将结束 3:演出结束,c03_mahjong_game,, diff --git a/asset/dialogue/event_stage.csv.import b/asset/dialogue/event_stage.csv.import new file mode 100644 index 00000000..1d6b13b9 --- /dev/null +++ b/asset/dialogue/event_stage.csv.import @@ -0,0 +1,17 @@ +[remap] + +importer="csv_translation" +type="Translation" +uid="uid://biw8l6b4d3v3j" + +[deps] + +files=["res://asset/dialogue/event_stage.zh_CN.translation"] + +source_file="res://asset/dialogue/event_stage.csv" +dest_files=["res://asset/dialogue/event_stage.zh_CN.translation"] + +[params] + +compress=true +delimiter=0 diff --git a/asset/dialogue/event_stage.dialogue b/asset/dialogue/event_stage.dialogue index 7a301ca2..442a6df9 100644 --- a/asset/dialogue/event_stage.dialogue +++ b/asset/dialogue/event_stage.dialogue @@ -13,8 +13,10 @@ c02_ball_game_stage: 0:初始化 1:寻找弹珠_老虎钳可以换弹珠 2:给 c03_s01_meat_put: 0:初始化 1:已放肉 c03_invite_xchan_supper: 0:初始化 1:已偷听_需邀请 2:完成邀请 c03_laizi_braid: 0:初始化 1:已使用剪刀 2:已剪下 +c03_f2_madman_runaway: 0:初始化 1:跑开_纸人挡路 2:消除纸人 +c03_invite_xchan_supper=2 & c03_laizi_braid=2 -> c03_f2_madman_runaway=2 c03_before_mahjong_game: 0:初始化 1:粘舌头和刀把 2:给药 4:准备好进入游戏 -c03_mahjong_game: 0::麻将理牌 1:麻将出千 2:麻将结束 3:演出结束 +c03_mahjong_game: 0:麻将理牌 1:麻将出千 2:麻将结束 3:演出结束 => END ~ EventStage_c04 diff --git a/manager/event_manager/event_manager.gd b/manager/event_manager/event_manager.gd index 2bd5b879..3dd2abe7 100644 --- a/manager/event_manager/event_manager.gd +++ b/manager/event_manager/event_manager.gd @@ -5,12 +5,142 @@ extends Node ## 其他系统(如UI、角色状态机等)可以监听此信号来做出反应。 signal stage_updated(event_name: StringName, stage: int) +# 存储公式的字典。键是事件名称(StringName),值是当该事件更新时需要被评估的公式(Callable)数组。 +# {event_name: [formula_callable_1, formula_callable_2, ...]} +var formula_dict: Dictionary[StringName, Array] = {} + # 节点初始化时,异步加载并解析事件规则。 func _ready() -> void: + # 异步加载,避免阻塞主线程。 + # 使用 await a, b 语法等待协程完成。 + await _load_and_parse_formulas() stage_updated.connect(_on_stage_updated_for_handnote) +# 将加载和解析逻辑封装到一个独立的异步函数中,使 _ready 更清晰。 +func _load_and_parse_formulas() -> void: + var event_stage_resource = load("uid://dohpsb4jttuv1") as DialogueResource + if not event_stage_resource: + printerr("[EventManager] Failed to load event stage resource (uid://dohpsb4jttuv1).") + return + + print("[EventManager] Refreshing event formulas...") + for title in event_stage_resource.get_titles(): + # 使用 await 等待异步函数 get_lines 完成 + var lines = await Util.get_lines(event_stage_resource, title) + for line in lines: + var line_text: String = line.text.strip_edges() + # 跳过注释行和不包含"->"的行 + if line_text.begins_with("#") or not "->" in line_text: + continue + _build_and_add_formula(line_text) + print("[EventManager] Formula refreshing completed.") + + +# 解析单行规则,构建公式并添加到字典中。 +# 示例: "c03_invite_xchan_supper=2 & c03_laizi_braid=2 -> c03_f2_madman_runaway=2" +func _build_and_add_formula(line_text: String) -> void: + var parts: PackedStringArray = line_text.split("->") + if parts.size() != 2: + printerr("[EventManager] Invalid formula line (must contain one '->'): %s" % line_text) + return + + var condition_events: Array[StringName] = [] + var condition_callable := _build_condition(parts[0], condition_events) + var execution_callable := _build_execution(parts[1]) + + # 关键的健壮性检查:确保条件和执行都成功构建 + if not condition_callable.is_valid() or not execution_callable.is_valid(): + printerr("[EventManager] Failed to build formula due to invalid parts from line: %s" % line_text) + return + + var formula := func(): + if condition_callable.call(): + if Engine.is_editor_hint() or GlobalConfig.DEBUG: + print("[EventManager] Formula condition met, executing: ", line_text) + execution_callable.call() + + if Engine.is_editor_hint() or GlobalConfig.DEBUG: + print("[EventManager] Built formula from: ", line_text) + + for event_name in condition_events: + if not formula_dict.has(event_name): + formula_dict[event_name] = [] + formula_dict[event_name].append(formula) + if Engine.is_editor_hint() or GlobalConfig.DEBUG: + print("[EventManager] Added formula for event: ", event_name) + + +# 构建条件 Callable。重构为迭代,而不是递归,以提高清晰度和健壮性。 +# 示例: "c03_invite_xchan_supper=2 & c03_laizi_braid=2" +func _build_condition(text: String, r_condition_events: Array[StringName]) -> Callable: + var condition_text := text.strip_edges() + if condition_text.is_empty(): + return func(): return true # 空条件始终为真 + + var sub_conditions: Array[Callable] = [] + # 按 "&" 分割所有子条件 + for part in condition_text.split("&"): + var single_condition_text := part.strip_edges() + if single_condition_text.is_empty(): + continue # 忽略由 "&&" 或末尾 "&" 产生的空部分 + + var args := single_condition_text.split("=", false, 1) + if args.size() != 2 or args[0].strip_edges().is_empty(): + printerr("[EventManager] Invalid condition part (format: 'event=value'): %s" % single_condition_text) + continue # 跳过此错误部分,继续解析下一个 + + var event_name_str := args[0].strip_edges() + var value_str := args[1].strip_edges() + + if not value_str.is_valid_int(): + printerr("[EventManager] Invalid integer value in condition: %s" % single_condition_text) + continue # 跳过 + + var event_name := StringName(event_name_str) + var target_value := value_str.to_int() + + if not r_condition_events.has(event_name): + r_condition_events.append(event_name) + + sub_conditions.append(func(): return get_stage(event_name) == target_value) + + if sub_conditions.is_empty(): + printerr("[EventManager] No valid conditions were parsed from text: ", text) + return Callable() # 返回无效 Callable,表示构建失败 + + # 返回一个复合 Callable,它会检查所有子条件是否都为真 + return func(): + for sub_condition in sub_conditions: + if not sub_condition.call(): + return false # 短路:任何一个为假,则整个条件为假 + return true # 所有子条件都为真 + + +# 构建执行 Callable。增加了健壮性检查。 +# 示例: "c03_f2_madman_runaway=2" +func _build_execution(text: String) -> Callable: + var execution_text := text.strip_edges() + var parts := execution_text.split("=", false, 1) + + if parts.size() != 2 or parts[0].strip_edges().is_empty(): + printerr("[EventManager] Invalid execution format (format: 'event=value'): %s" % execution_text) + return Callable() # 返回无效 Callable + + var event_name_str := parts[0].strip_edges() + var value_str := parts[1].strip_edges() + + if not value_str.is_valid_int(): + printerr("[EventManager] Invalid integer value in execution: %s" % execution_text) + return Callable() # 返回无效 Callable + + var event_name := StringName(event_name_str) + var target_value := value_str.to_int() + + # 使用 .bind() 是创建带参数 Callable 的首选方式,比手动包装一个 lambda 更清晰高效。 + return set_stage_if_greater.bind(event_name, target_value) + # --- 公共 API --- func get_chapter_stage() -> int: @@ -32,7 +162,12 @@ func set_stage(event_name: StringName, stage := 1) -> void: return ArchiveManager.archive.event_stage[event_name] = stage print("[EventManager] Stage updated: %s -> %s" % [event_name, stage]) + # 1. 发出通用信号 stage_updated.emit(event_name, stage) + # 2. 触发关联的公式 + if formula_dict.has(event_name): + for formula in formula_dict.get(event_name, []): + formula.call() # 仅当设置的 stage > 当前 stage 时更新 @@ -96,12 +231,13 @@ func get_event_stage_map_array(event_name: StringName) -> PackedStringArray: return current_dict["stages"] # 文件: res://asset/dialogue/event_stage.dialogue var event_stage_resource = load("uid://dohpsb4jttuv1") as DialogueResource + print("[EventManager] get_event_stage_map_array refreshing...") for title in event_stage_resource.get_titles(): var lines = await Util.get_lines(event_stage_resource, title) for line in lines: var line_text = line.text.strip_edges() var e_name = line.character - if line_text.begins_with("#"): + if line_text.begins_with("#") or line_text.find("->") >= 0 or not e_name: continue #xxxx: 0:未开始 1:已偷听,需邀请 2:已完成邀请 var parts = line_text.split(" ")