Files
Fighting_Rthythm_game/scenes/combat/action_resolver.gd
2026-07-02 09:47:52 -07:00

171 lines
5.2 KiB
GDScript

class_name ActionResolver
extends Node
const ACTION_DIR := "res://resources/actions"
static var _loaded := false
static var _actions_by_pattern: Dictionary = {}
static var _actions_by_id: Dictionary = {}
static var _space_priority_labels: Array[StringName] = [
&"charge_release",
&"burst",
&"counter_projectile",
&"blade_chain",
&"state_specific",
&"exact_pattern",
&"fallback",
]
func resolve_window(window: Variant, state_machine: Variant = null, context: Dictionary = {}) -> Resource:
return ActionResolver.resolve(window, state_machine, context)
func resolve_text_pattern(pattern: String, state_machine: Variant = null, context: Dictionary = {}) -> Resource:
return ActionResolver.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 = {}) -> Resource:
_ensure_loaded()
var key := _normalize_pattern(pattern)
if key.ends_with("SP"):
var priority_result := _resolve_space_priority(key, state_machine, context)
if priority_result != null:
return priority_result
var candidates: Array = _actions_by_pattern.get(key, [])
var state := _state_name(state_machine, context)
for action: Resource in candidates:
if _can_use_in_state(action, state):
return action
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 files := dir.get_files()
files.sort()
for file_name: String in files:
if not file_name.ends_with(".tres"):
continue
var action: Resource = load("%s/%s" % [action_dir, file_name])
if action == null or not action.get("input_pattern") is Array:
continue
_register_action(action)
static func _register_action(action: Resource) -> void:
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
var id := StringName(str(action.get("id")))
if not id.is_empty():
_actions_by_id[id] = action
static func _resolve_space_priority(key: String, state_machine: Variant, context: Dictionary) -> Resource:
var state := _state_name(state_machine, context)
for action_id_key: String in [
"charge_release_action_id",
"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_use_in_state(explicit_action, state):
return explicit_action
var candidates: Array = _actions_by_pattern.get(key, [])
for action: Resource in candidates:
if _can_use_in_state(action, state):
return action
var suffix_action := _resolve_trailing_space_suffix(key, state)
if suffix_action != null:
return suffix_action
return null
static func _resolve_trailing_space_suffix(key: String, state: StringName) -> 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_use_in_state(action, state):
best_action = action
best_length = candidate_key.length()
break
return best_action
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 _state_name(state_machine: Variant, context: Dictionary) -> StringName:
if context.has("state"):
return StringName(str(context["state"]))
if state_machine != null and state_machine.has_method("get_current_state_name"):
return StringName(str(state_machine.call("get_current_state_name")))
return &"any"
static func _can_use_in_state(action: Resource, state: StringName) -> bool:
var required := StringName(str(action.get("required_state")))
return required.is_empty() or required == &"any" or state == &"any" or required == state