171 lines
5.2 KiB
GDScript
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
|