xiandie/manager/event_manager/event_manager.gd

287 lines
10 KiB
GDScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@tool
extends Node
## 当任何事件的状态stage发生变化时发出此信号。
## 其他系统如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_s03_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_s03_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:
return get_stage(&"current_chapter_stage")
func get_stage(event_name: StringName) -> int:
if not ArchiveManager.archive or ArchiveManager.archive.event_stage == null:
printerr("[EventManager] Archive or event_stage is null. Cannot get stage for '", event_name, "'.")
return 0
return ArchiveManager.archive.event_stage.get(event_name, 0)
# 核心的 stage 设置函数
# default 为 0首次更新为 1
func set_stage(event_name: StringName, stage := 1) -> void:
if not ArchiveManager.archive or ArchiveManager.archive.event_stage == null:
printerr("[EventManager] Archive or event_stage is null. Cannot set stage for '", event_name, "'.")
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 时更新
func set_stage_if_greater(event_name: StringName, stage: int) -> bool:
if stage > get_stage(event_name):
set_stage(event_name, stage)
return true
return false
# stage 最大 99999
func next_stage(event_name: StringName, stage_max := 99999) -> int:
var current_stage = get_stage(event_name)
if current_stage < stage_max:
var new_stage = current_stage + 1
set_stage(event_name, new_stage)
return new_stage
return stage_max
# --- 游戏特定逻辑 ---
func _on_stage_updated_for_handnote(event_name: StringName, stage: int) -> void:
# 检查条件是否满足
if not (
SceneManager.is_node_ready()
and SceneManager.get_player()
and SceneManager.get_player().character.begins_with("吕萍")
):
return
if event_name.begins_with("handnote_"):
# 笔记条目更新
# 0 初始化隐藏1 开始显示2 划掉3 结束隐藏
if stage == 1:
SceneManager.lock_player(3.0, 16, true)
SceneManager.pop_notification("ui_notify_note_update")
##### 其他事件 #####
func prop_interacted(e_name, prop_key, interacted_times) -> void:
print("Event: %s interacted with %s. total times: %s" % [e_name, prop_key, interacted_times])
##### TOOL 方法
# event_name -> {stages:PackedStringArray, update_time:int}
var _debug_event_stage_dict := {}
var _event_stage_map_update_time := 0
var _event_stage_map_mutex := Mutex.new()
# return: stages
func get_event_stage_map_array(event_name: StringName) -> PackedStringArray:
if not Engine.is_editor_hint():
return PackedStringArray()
var current_dict = _debug_event_stage_dict.get(event_name)
var time_msec := Time.get_ticks_msec()
# 3秒更新
_event_stage_map_mutex.lock()
if time_msec - _event_stage_map_update_time < 3000:
_event_stage_map_mutex.unlock()
if current_dict:
return current_dict["stages"]
else:
return PackedStringArray()
_event_stage_map_update_time = time_msec
# 文件: 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("#") or line_text.find("->") >= 0 or not e_name:
continue
#xxxx: 0:未开始 1:已偷听,需邀请 2:已完成邀请
var parts = line_text.split(" ")
var stages = PackedStringArray()
for id in range(0, len(parts)):
var tuple = parts[id].split(":")
if len(tuple) == 2:
var event_stage = int(tuple[0])
if stages.size() < event_stage + 1:
stages.resize(event_stage + 1)
stages[event_stage] = tuple[1]
for id in len(stages):
if stages[id] == "":
stages[id] = str(id)
_debug_event_stage_dict[e_name] = {
"stages": stages,
}
_event_stage_map_mutex.unlock()
if _debug_event_stage_dict.has(event_name):
prints("reload", event_name, "stages:", _debug_event_stage_dict[event_name]["stages"])
return _debug_event_stage_dict[event_name]["stages"]
return PackedStringArray()
# map stage_id->name, 必定保持相同长度的数组
func map_event_stages(
event_name: StringName, stages: Array, including_id := true
) -> PackedStringArray:
var result = PackedStringArray()
result.resize(stages.size())
var dict = await get_event_stage_map_array(event_name)
for id in range(stages.size()):
var stage = stages[id]
if dict.size() > stage:
if including_id:
result[id] = str(stage) + ":" + dict[stage]
else:
result[id] = str(stage)
return result