Initial project sync
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user