198 lines
6.3 KiB
GDScript
198 lines
6.3 KiB
GDScript
class_name ActionResolver
|
|
extends Node
|
|
|
|
const ACTION_DIR := "res://resources/actions"
|
|
const ActionRuleResolverScript := preload("res://scripts/resolvers/action_rule_resolver.gd")
|
|
|
|
const BASIC_TRAILING_SUFFIXES := ["W", "A", "D", "S"]
|
|
|
|
static var _loaded := false
|
|
static var _actions_by_pattern: Dictionary = {}
|
|
static var _actions_by_id: Dictionary = {}
|
|
static var _space_priority_labels: Array[StringName] = [
|
|
&"burst",
|
|
&"counter_projectile",
|
|
&"blade_chain",
|
|
&"state_specific",
|
|
&"exact_pattern",
|
|
&"fallback",
|
|
]
|
|
|
|
|
|
func resolve_window(window: Variant, state_machine: Variant = null, context: Dictionary = {}) -> Resource:
|
|
return resolve(window, state_machine, context)
|
|
|
|
|
|
func resolve_text_pattern(pattern: String, state_machine: Variant = null, context: Dictionary = {}) -> Resource:
|
|
return resolve_pattern(pattern, state_machine, context)
|
|
|
|
|
|
static func resolve(window: Variant, state_machine: Variant = null, context: Dictionary = {}) -> Resource:
|
|
return resolve_pattern(window.get_contiguous_pattern(), state_machine, context)
|
|
|
|
|
|
static func resolve_pattern(pattern: String, state_machine: Variant = null, context: Dictionary = {}, allow_basic_trailing_fallback := true) -> Resource:
|
|
_ensure_loaded()
|
|
var key := _normalize_pattern(pattern)
|
|
var resolver_context := _resolver_context(state_machine, context)
|
|
if key.ends_with("SP"):
|
|
var priority_result := _resolve_space_priority(key, resolver_context)
|
|
if priority_result != null:
|
|
return priority_result
|
|
var candidates: Array = _actions_by_pattern.get(key, [])
|
|
for action: Resource in candidates:
|
|
if _can_execute(action, resolver_context):
|
|
return action
|
|
var trailing_basic := _resolve_trailing_basic_suffix(key, resolver_context, allow_basic_trailing_fallback)
|
|
if trailing_basic != null:
|
|
return trailing_basic
|
|
return null
|
|
|
|
|
|
static func get_action(action_id: StringName) -> Resource:
|
|
_ensure_loaded()
|
|
return _actions_by_id.get(action_id, null) as Resource
|
|
|
|
|
|
static func space_priority_labels() -> Array[StringName]:
|
|
return _space_priority_labels.duplicate()
|
|
|
|
|
|
static func reload(action_dir := ACTION_DIR) -> void:
|
|
_loaded = true
|
|
_actions_by_pattern.clear()
|
|
_actions_by_id.clear()
|
|
_load_dir(action_dir)
|
|
|
|
|
|
static func clear_cache() -> void:
|
|
_actions_by_pattern.clear()
|
|
_actions_by_id.clear()
|
|
_loaded = false
|
|
|
|
|
|
static func _ensure_loaded() -> void:
|
|
if not _loaded:
|
|
reload()
|
|
|
|
|
|
static func _load_dir(action_dir: String) -> void:
|
|
var dir := DirAccess.open(action_dir)
|
|
if dir == null:
|
|
return
|
|
var subdirs := dir.get_directories()
|
|
subdirs.sort()
|
|
for subdir: String in subdirs:
|
|
_load_dir("%s/%s" % [action_dir, subdir])
|
|
# 导出包(pck)内的目录列表把资源显示为 xxx.tres.remap,直接按 .tres
|
|
# 过滤会一个都匹配不上,导致动作库为空(玩家出招和敌人攻击全部失效)。
|
|
# 还原原始文件名再 load(),remap 表会命中 pck 内的实际资源。
|
|
var seen := {}
|
|
var files := dir.get_files()
|
|
files.sort()
|
|
for file_name: String in files:
|
|
var resource_name := file_name.trim_suffix(".remap")
|
|
if not resource_name.ends_with(".tres") or seen.has(resource_name):
|
|
continue
|
|
seen[resource_name] = true
|
|
var action: Resource = load("%s/%s" % [action_dir, resource_name])
|
|
if action == null or not action.get("input_pattern") is Array:
|
|
continue
|
|
_register_action(action)
|
|
|
|
|
|
static func _register_action(action: Resource) -> void:
|
|
var id := StringName(str(action.get("id")))
|
|
if not id.is_empty():
|
|
_actions_by_id[id] = action
|
|
var key := _pattern_key(action.get("input_pattern"))
|
|
if key.is_empty():
|
|
return
|
|
var candidates: Array = _actions_by_pattern.get(key, [])
|
|
candidates.append(action)
|
|
_actions_by_pattern[key] = candidates
|
|
|
|
|
|
static func _resolve_space_priority(key: String, context: Dictionary) -> Resource:
|
|
for action_id_key: String in [
|
|
"burst_action_id",
|
|
"counter_action_id",
|
|
"blade_chain_action_id",
|
|
]:
|
|
if not context.has(action_id_key):
|
|
continue
|
|
if action_id_key == "counter_action_id" and not bool(context.get("counter_ready", false)):
|
|
continue
|
|
if action_id_key == "blade_chain_action_id" and not bool(context.get("blade_chain_active", false)):
|
|
continue
|
|
var explicit_action := get_action(StringName(str(context[action_id_key])))
|
|
if explicit_action != null and _can_execute(explicit_action, context):
|
|
return explicit_action
|
|
|
|
var candidates: Array = _actions_by_pattern.get(key, [])
|
|
for action: Resource in candidates:
|
|
if _can_execute(action, context):
|
|
return action
|
|
var suffix_action := _resolve_trailing_space_suffix(key, context)
|
|
if suffix_action != null:
|
|
return suffix_action
|
|
return null
|
|
|
|
|
|
static func _resolve_trailing_space_suffix(key: String, context: Dictionary) -> Resource:
|
|
var best_action: Resource = null
|
|
var best_length := 0
|
|
for candidate_key: String in _actions_by_pattern.keys():
|
|
if candidate_key == key:
|
|
continue
|
|
if not candidate_key.ends_with("SP"):
|
|
continue
|
|
if candidate_key.length() <= best_length:
|
|
continue
|
|
if not key.ends_with(candidate_key):
|
|
continue
|
|
for action: Resource in _actions_by_pattern.get(candidate_key, []):
|
|
if _can_execute(action, context):
|
|
best_action = action
|
|
best_length = candidate_key.length()
|
|
break
|
|
return best_action
|
|
|
|
|
|
static func _resolve_trailing_basic_suffix(key: String, context: Dictionary, allow_basic_trailing_fallback: bool) -> Resource:
|
|
if not allow_basic_trailing_fallback:
|
|
return null
|
|
for candidate_key: String in BASIC_TRAILING_SUFFIXES:
|
|
if key == candidate_key:
|
|
continue
|
|
if not key.ends_with(candidate_key):
|
|
continue
|
|
for action: Resource in _actions_by_pattern.get(candidate_key, []):
|
|
if _can_execute(action, context):
|
|
return action
|
|
return null
|
|
|
|
|
|
static func _pattern_key(pattern: Array[StringName]) -> String:
|
|
var key := ""
|
|
for symbol: StringName in pattern:
|
|
key += str(symbol)
|
|
return _normalize_pattern(key)
|
|
|
|
|
|
static func _normalize_pattern(pattern: String) -> String:
|
|
return pattern.replace(" ", "").to_upper()
|
|
|
|
|
|
static func _resolver_context(state_machine: Variant, context: Dictionary) -> Dictionary:
|
|
var result := context.duplicate()
|
|
if state_machine != null and state_machine.has_method("build_context"):
|
|
for key: Variant in state_machine.call("build_context").keys():
|
|
if not result.has(key):
|
|
result[key] = state_machine.call("build_context")[key]
|
|
return result
|
|
|
|
|
|
static func _can_execute(action: Resource, context: Dictionary) -> bool:
|
|
return ActionRuleResolverScript.can_execute(context, action)
|