mirror of
https://gitlab.com/MaddoScientisto/cirnogodot.git
synced 2026-06-01 10:25:35 +00:00
281 lines
9.1 KiB
GDScript
281 lines
9.1 KiB
GDScript
@tool
|
|
extends RefCounted
|
|
|
|
const SUPPORTED_SCRIPT_SUFFIXES := [
|
|
"BossScript.cs",
|
|
"BossPhase.cs",
|
|
"BulletScript3D.cs",
|
|
"BulletScript.cs",
|
|
"AttackPattern.cs",
|
|
"PatternGroup.cs",
|
|
"ParallelPatternGroup.cs",
|
|
"MovementPattern.cs"
|
|
]
|
|
|
|
func is_supported_resource(resource: Resource) -> bool:
|
|
if resource == null:
|
|
return false
|
|
var script: Script = resource.get_script()
|
|
var path := ""
|
|
if script != null:
|
|
path = script.resource_path
|
|
if path != "" and path.contains("/Scripts/AttackPatterns/"):
|
|
return true
|
|
if path != "" and path.contains("/Scripts/Resources/") and path.contains("Pattern"):
|
|
return true
|
|
for suffix in SUPPORTED_SCRIPT_SUFFIXES:
|
|
if path != "" and path.ends_with(suffix):
|
|
return true
|
|
# Heuristic for AttackPattern-derived resources exposed from C# exports.
|
|
if resource.get("WaitForCompletion") != null:
|
|
return true
|
|
return false
|
|
|
|
func build_graph(root: Resource) -> Dictionary:
|
|
var graph := {
|
|
"nodes": {},
|
|
"edges": [],
|
|
"edge_index": {},
|
|
"root_id": ""
|
|
}
|
|
if root == null:
|
|
return graph
|
|
|
|
var ctx := {
|
|
"graph": graph,
|
|
"visited": {}
|
|
}
|
|
var root_id := _visit_resource(root, "", "root", "root", ctx)
|
|
graph.erase("edge_index")
|
|
graph["root_id"] = root_id
|
|
return graph
|
|
|
|
func _visit_resource(resource: Resource, parent_id: String, edge_type: String, label: String, ctx: Dictionary) -> String:
|
|
var id := _make_node_id(resource)
|
|
var nodes: Dictionary = ctx["graph"]["nodes"]
|
|
|
|
if not nodes.has(id):
|
|
nodes[id] = {
|
|
"id": id,
|
|
"title": _make_title(resource, label),
|
|
"subtitle": _make_subtitle(resource),
|
|
"kind": _get_kind(resource),
|
|
"resource": resource,
|
|
"incoming": [],
|
|
"outgoing": []
|
|
}
|
|
|
|
if parent_id != "":
|
|
_add_edge(parent_id, id, edge_type, ctx)
|
|
|
|
var visited: Dictionary = ctx["visited"]
|
|
if visited.has(id):
|
|
return id
|
|
visited[id] = true
|
|
|
|
_process_children(resource, id, ctx)
|
|
return id
|
|
|
|
func _add_edge(from_id: String, to_id: String, edge_type: String, ctx: Dictionary) -> void:
|
|
var edge_key := "%s|%s|%s" % [from_id, to_id, edge_type]
|
|
var edge_index: Dictionary = ctx["graph"]["edge_index"]
|
|
if edge_index.has(edge_key):
|
|
return
|
|
edge_index[edge_key] = true
|
|
ctx["graph"]["edges"].append({
|
|
"from": from_id,
|
|
"to": to_id,
|
|
"type": edge_type
|
|
})
|
|
|
|
var nodes: Dictionary = ctx["graph"]["nodes"]
|
|
if nodes.has(from_id):
|
|
nodes[from_id]["outgoing"].append(edge_type)
|
|
if nodes.has(to_id):
|
|
nodes[to_id]["incoming"].append(edge_type)
|
|
|
|
func _ensure_virtual_node(id: String, title: String, subtitle: String, kind: String, ctx: Dictionary) -> void:
|
|
var nodes: Dictionary = ctx["graph"]["nodes"]
|
|
if nodes.has(id):
|
|
return
|
|
nodes[id] = {
|
|
"id": id,
|
|
"title": title,
|
|
"subtitle": subtitle,
|
|
"kind": kind,
|
|
"resource": null,
|
|
"incoming": [],
|
|
"outgoing": []
|
|
}
|
|
|
|
func _process_children(resource: Resource, id: String, ctx: Dictionary) -> void:
|
|
var kind := _get_kind(resource)
|
|
|
|
if kind == "BossScript":
|
|
_visit_ordered_array(resource, "Phases", id, "phase", "phase", ctx)
|
|
return
|
|
|
|
if kind == "BossPhase":
|
|
var bullet_lane_id := "%s::bullet_lane" % id
|
|
var legacy_lane_id := "%s::legacy_lane" % id
|
|
|
|
var bullet_script: Variant = _get_prop(resource, "BulletScript3D")
|
|
if bullet_script is Resource:
|
|
_ensure_virtual_node(bullet_lane_id, "BulletScript3D Lane", "Preferred execution path", "Lane", ctx)
|
|
_add_edge(id, bullet_lane_id, "bullet_script_lane", ctx)
|
|
_visit_resource(bullet_script, bullet_lane_id, "bullet_script", "BulletScript3D", ctx)
|
|
|
|
var legacy_values: Array = _get_array_prop(resource, "Patterns")
|
|
if legacy_values.size() > 0:
|
|
_ensure_virtual_node(legacy_lane_id, "Legacy Patterns Lane", "Compatibility path", "LegacyLane", ctx)
|
|
_add_edge(id, legacy_lane_id, "legacy_lane", ctx)
|
|
_visit_ordered_array(resource, "Patterns", legacy_lane_id, "legacy", "pattern", ctx)
|
|
return
|
|
|
|
if kind == "BulletScript3D" or kind == "BulletScript":
|
|
_visit_ordered_array(resource, "Patterns", id, "sequence", "pattern", ctx)
|
|
return
|
|
|
|
if kind == "PatternGroup":
|
|
_visit_ordered_array_fallback(resource, ["Patterns", "patterns"], id, "group", "child", ctx)
|
|
return
|
|
|
|
if kind == "ParallelPatternGroup":
|
|
_visit_parallel_array(resource, "Patterns", id, "parallel", "child", ctx)
|
|
return
|
|
|
|
if kind == "MovementPattern":
|
|
var child: Variant = _get_prop(resource, "shootingPattern")
|
|
if child is Resource:
|
|
_visit_resource(child, id, "embedded", "shootingPattern", ctx)
|
|
return
|
|
|
|
_process_common_links(resource, id, ctx)
|
|
|
|
func _process_common_links(resource: Resource, id: String, ctx: Dictionary) -> void:
|
|
for property_name in ["BulletScript3D", "BulletScript", "Script", "shootingPattern"]:
|
|
var linked: Variant = _get_prop(resource, property_name)
|
|
if linked is Resource:
|
|
_visit_resource(linked, id, "reference", property_name, ctx)
|
|
|
|
# Generic fallback for pattern arrays in custom pattern resources.
|
|
for array_name in ["Patterns", "patterns"]:
|
|
var values := _get_array_prop(resource, array_name)
|
|
if values.size() > 0:
|
|
_visit_ordered_array_fallback(resource, [array_name], id, "sequence", "pattern", ctx)
|
|
return
|
|
|
|
func _visit_ordered_array(owner: Resource, property_name: String, parent_id: String, first_edge_type: String, label_prefix: String, ctx: Dictionary) -> void:
|
|
var values: Array = _get_array_prop(owner, property_name)
|
|
var prev := parent_id
|
|
for i in values.size():
|
|
var item = values[i]
|
|
if not (item is Resource):
|
|
continue
|
|
var edge_type := first_edge_type if prev == parent_id else "sequence"
|
|
var label := "%s[%d]" % [label_prefix, i]
|
|
prev = _visit_resource(item, prev, edge_type, label, ctx)
|
|
|
|
func _visit_ordered_array_fallback(owner: Resource, property_names: Array, parent_id: String, first_edge_type: String, label_prefix: String, ctx: Dictionary) -> void:
|
|
for property_name in property_names:
|
|
var values: Array = _get_array_prop(owner, property_name)
|
|
if values.size() == 0:
|
|
continue
|
|
var prev := parent_id
|
|
for i in values.size():
|
|
var item = values[i]
|
|
if not (item is Resource):
|
|
continue
|
|
var edge_type := first_edge_type if prev == parent_id else "sequence"
|
|
var label := "%s[%d]" % [label_prefix, i]
|
|
prev = _visit_resource(item, prev, edge_type, label, ctx)
|
|
return
|
|
|
|
func _visit_parallel_array(owner: Resource, property_name: String, parent_id: String, edge_type: String, label_prefix: String, ctx: Dictionary) -> void:
|
|
var values: Array = _get_array_prop(owner, property_name)
|
|
for i in values.size():
|
|
var item = values[i]
|
|
if not (item is Resource):
|
|
continue
|
|
var label := "%s[%d]" % [label_prefix, i]
|
|
_visit_resource(item, parent_id, edge_type, label, ctx)
|
|
|
|
func _get_array_prop(resource: Resource, property_name: String) -> Array:
|
|
var value = _get_prop(resource, property_name)
|
|
return value if value is Array else []
|
|
|
|
func _get_prop(resource: Resource, property_name: String):
|
|
if resource == null:
|
|
return null
|
|
if not _has_property(resource, property_name):
|
|
return null
|
|
if resource.has_method("get"):
|
|
return resource.get(property_name)
|
|
return null
|
|
|
|
func _has_property(resource: Object, property_name: String) -> bool:
|
|
for property_data in resource.get_property_list():
|
|
if property_data is Dictionary and property_data.get("name", "") == property_name:
|
|
return true
|
|
return false
|
|
|
|
func _make_node_id(resource: Resource) -> String:
|
|
if resource.resource_path != "":
|
|
return resource.resource_path
|
|
return "%s:%s" % [_get_kind(resource), str(resource.get_instance_id())]
|
|
|
|
func _make_title(resource: Resource, label: String) -> String:
|
|
var kind := _get_kind(resource)
|
|
# Prefer explicit Title property if present
|
|
var title_prop = _get_prop(resource, "Title")
|
|
if title_prop != null and str(title_prop) != "":
|
|
return "%s" % str(title_prop)
|
|
|
|
if kind == "BossScript":
|
|
var boss_name = _get_prop(resource, "BossName")
|
|
if boss_name != null and str(boss_name) != "":
|
|
return "BossScript: %s" % str(boss_name)
|
|
if kind == "BossPhase":
|
|
var phase_name = _get_prop(resource, "PhaseName")
|
|
if phase_name != null and str(phase_name) != "":
|
|
return "BossPhase: %s" % str(phase_name)
|
|
if label != "" and label != "root":
|
|
return "%s (%s)" % [kind, label]
|
|
return kind
|
|
|
|
func _make_subtitle(resource: Resource) -> String:
|
|
# Prefer Description property when available
|
|
var desc = _get_prop(resource, "Description")
|
|
if desc != null and str(desc) != "":
|
|
return str(desc)
|
|
if resource.resource_path != "":
|
|
return resource.resource_path
|
|
return "subresource"
|
|
|
|
func _get_kind(resource: Resource) -> String:
|
|
var script: Script = resource.get_script()
|
|
if script == null:
|
|
return resource.get_class()
|
|
|
|
var path := script.resource_path
|
|
if path.ends_with("BossScript.cs"):
|
|
return "BossScript"
|
|
if path.ends_with("BossPhase.cs"):
|
|
return "BossPhase"
|
|
if path.ends_with("BulletScript3D.cs"):
|
|
return "BulletScript3D"
|
|
if path.ends_with("BulletScript.cs"):
|
|
return "BulletScript"
|
|
if path.ends_with("PatternGroup.cs"):
|
|
return "PatternGroup"
|
|
if path.ends_with("ParallelPatternGroup.cs"):
|
|
return "ParallelPatternGroup"
|
|
if path.ends_with("MovementPattern.cs"):
|
|
return "MovementPattern"
|
|
if path.ends_with("AttackPattern.cs"):
|
|
return "AttackPattern"
|
|
if path.contains("/Scripts/AttackPatterns/"):
|
|
return path.get_file().trim_suffix(".cs")
|
|
|
|
var file_name := path.get_file().trim_suffix(".cs")
|
|
return file_name if file_name != "" else resource.get_class()
|