Refactor rhythm action architecture
This commit is contained in:
170
scenes/combat/action_resolver.gd
Normal file
170
scenes/combat/action_resolver.gd
Normal file
@@ -0,0 +1,170 @@
|
||||
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
|
||||
1
scenes/combat/action_resolver.gd.uid
Normal file
1
scenes/combat/action_resolver.gd.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dyfn38jkq6ld8
|
||||
@@ -1,55 +0,0 @@
|
||||
class_name ComboWindow
|
||||
extends RefCounted
|
||||
|
||||
signal window_cleared(reason: String)
|
||||
|
||||
const SIZE := 4
|
||||
|
||||
var slots: Array[String] = []
|
||||
var pending_clear_reason := ""
|
||||
|
||||
|
||||
func record(input: String) -> void:
|
||||
if input.is_empty():
|
||||
return
|
||||
slots.append(input)
|
||||
if slots.size() >= SIZE:
|
||||
pending_clear_reason = "full"
|
||||
|
||||
|
||||
func get_slots() -> Array[String]:
|
||||
return slots.duplicate()
|
||||
|
||||
|
||||
func has_pending_clear() -> bool:
|
||||
return not pending_clear_reason.is_empty()
|
||||
|
||||
|
||||
func consume_pending_clear_reason() -> String:
|
||||
var reason := pending_clear_reason
|
||||
pending_clear_reason = ""
|
||||
return reason
|
||||
|
||||
|
||||
func get_pattern() -> String:
|
||||
var pattern := ""
|
||||
for slot: String in slots:
|
||||
if slot != "Ø":
|
||||
pattern += slot
|
||||
return pattern
|
||||
|
||||
|
||||
func get_contiguous_pattern() -> String:
|
||||
var pattern := ""
|
||||
for index: int in range(slots.size() - 1, -1, -1):
|
||||
var slot := slots[index]
|
||||
if slot == "Ø":
|
||||
break
|
||||
pattern = slot + pattern
|
||||
return pattern
|
||||
|
||||
|
||||
func clear(reason := "") -> void:
|
||||
slots.clear()
|
||||
pending_clear_reason = ""
|
||||
window_cleared.emit(reason)
|
||||
@@ -1 +0,0 @@
|
||||
uid://dtguxwnh02f6g
|
||||
@@ -1,157 +0,0 @@
|
||||
class_name InputResolver
|
||||
extends RefCounted
|
||||
|
||||
const SKILLS := {
|
||||
"W": {
|
||||
"type": "skill",
|
||||
"id": "skill_w",
|
||||
"animation": "warrior_w",
|
||||
"clear_window": false,
|
||||
},
|
||||
"A": {
|
||||
"type": "skill",
|
||||
"id": "skill_a",
|
||||
"animation": "warrior_a",
|
||||
"displacement": "left",
|
||||
"clear_window": false,
|
||||
},
|
||||
"D": {
|
||||
"type": "skill",
|
||||
"id": "skill_d",
|
||||
"animation": "warrior_a",
|
||||
"displacement": "right",
|
||||
"clear_window": false,
|
||||
},
|
||||
"WA": {
|
||||
"type": "skill",
|
||||
"id": "skill_wa",
|
||||
"animation": "warrior_wa",
|
||||
"displacement": "left",
|
||||
"clear_window": false,
|
||||
},
|
||||
"WD": {
|
||||
"type": "skill",
|
||||
"id": "skill_wd",
|
||||
"animation": "warrior_wa",
|
||||
"displacement": "right",
|
||||
"clear_window": false,
|
||||
},
|
||||
"AA": {
|
||||
"type": "skill",
|
||||
"id": "skill_aa",
|
||||
"animation": "warrior_aa",
|
||||
"displacement": "left",
|
||||
"clear_window": false,
|
||||
},
|
||||
"DD": {
|
||||
"type": "skill",
|
||||
"id": "skill_dd",
|
||||
"animation": "warrior_aa",
|
||||
"displacement": "right",
|
||||
"clear_window": false,
|
||||
},
|
||||
"AAA": {
|
||||
"type": "skill",
|
||||
"id": "skill_aaa",
|
||||
"animation": "warrior_aaa",
|
||||
"displacement": "left",
|
||||
"clear_window": false,
|
||||
},
|
||||
"DDD": {
|
||||
"type": "skill",
|
||||
"id": "skill_ddd",
|
||||
"animation": "warrior_aaa",
|
||||
"displacement": "right",
|
||||
"clear_window": false,
|
||||
},
|
||||
"ASP": {
|
||||
"type": "skill",
|
||||
"id": "skill_a_space",
|
||||
"animation": "warrior_a_space",
|
||||
"displacement": "left",
|
||||
"clear_window": true,
|
||||
},
|
||||
"DSP": {
|
||||
"type": "skill",
|
||||
"id": "skill_d_space",
|
||||
"animation": "warrior_a_space",
|
||||
"displacement": "right",
|
||||
"clear_window": true,
|
||||
},
|
||||
"ASPSP": {
|
||||
"type": "skill",
|
||||
"id": "skill_a_space_space",
|
||||
"animation": "warrior_a_space_space",
|
||||
"displacement": "left",
|
||||
"clear_window": true,
|
||||
},
|
||||
"DSPSP": {
|
||||
"type": "skill",
|
||||
"id": "skill_d_space_space",
|
||||
"animation": "warrior_a_space_space",
|
||||
"displacement": "right",
|
||||
"clear_window": true,
|
||||
},
|
||||
"AASP": {
|
||||
"type": "skill",
|
||||
"id": "skill_aa_space",
|
||||
"animation": "warrior_a_space_space",
|
||||
"displacement": "left",
|
||||
"clear_window": true,
|
||||
},
|
||||
"ADSP": {
|
||||
"type": "skill",
|
||||
"id": "skill_ad_space",
|
||||
"animation": "warrior_a_space_space",
|
||||
"displacement": "right",
|
||||
"clear_window": true,
|
||||
},
|
||||
"DASP": {
|
||||
"type": "skill",
|
||||
"id": "skill_da_space",
|
||||
"animation": "warrior_a_space_space",
|
||||
"displacement": "left",
|
||||
"clear_window": true,
|
||||
},
|
||||
"DDSP": {
|
||||
"type": "skill",
|
||||
"id": "skill_dd_space",
|
||||
"animation": "warrior_a_space_space",
|
||||
"displacement": "right",
|
||||
"clear_window": true,
|
||||
},
|
||||
"SSP": {
|
||||
"type": "skill",
|
||||
"id": "skill_s_projectile_1",
|
||||
"animation": "warrior_s_projectile",
|
||||
"projectile": true,
|
||||
"energy_cost": 3,
|
||||
"clear_window": false,
|
||||
},
|
||||
"SSPSP": {
|
||||
"type": "skill",
|
||||
"id": "skill_s_projectile_2",
|
||||
"animation": "warrior_s_projectile",
|
||||
"projectile": true,
|
||||
"energy_cost": 2,
|
||||
"clear_window": false,
|
||||
},
|
||||
"SSPSPSP": {
|
||||
"type": "skill",
|
||||
"id": "skill_s_projectile_3",
|
||||
"animation": "warrior_s_projectile",
|
||||
"projectile": true,
|
||||
"energy_cost": 1,
|
||||
"clear_window": false,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
static func resolve(window: ComboWindow) -> Dictionary:
|
||||
return resolve_pattern(window.get_contiguous_pattern())
|
||||
|
||||
|
||||
static func resolve_pattern(pattern: String) -> Dictionary:
|
||||
if not SKILLS.has(pattern):
|
||||
return {}
|
||||
return SKILLS[pattern].duplicate()
|
||||
@@ -1 +0,0 @@
|
||||
uid://cyhq381jiyo42
|
||||
6
scenes/combat/player_projectile.tscn
Normal file
6
scenes/combat/player_projectile.tscn
Normal file
@@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://scenes/combat/player_projectile.gd" id="1"]
|
||||
|
||||
[node name="PlayerProjectile" type="Node2D"]
|
||||
script = ExtResource("1")
|
||||
Reference in New Issue
Block a user