forked from joey/godottest
204 lines
5.6 KiB
GDScript
204 lines
5.6 KiB
GDScript
extends RefCounted
|
|
class_name SS2D_VersionTransition
|
|
|
|
|
|
class IVersionConverter:
|
|
extends RefCounted
|
|
|
|
# Initialize internal state. Must be called before using any other functionality.
|
|
func init() -> void:
|
|
pass
|
|
|
|
func needs_conversion() -> bool:
|
|
return false
|
|
|
|
# Perform conversion. Should return true and do nothing if no conversion is needed.
|
|
func convert() -> bool:
|
|
return true
|
|
|
|
|
|
class ShapeNodeTypeConverter:
|
|
extends IVersionConverter
|
|
|
|
var _files: PackedStringArray
|
|
var _from_type: String
|
|
var _to_type: String
|
|
|
|
func _init(from: String, to: String) -> void:
|
|
_from_type = from
|
|
_to_type = to
|
|
|
|
func init() -> void:
|
|
var analyzer := TscnAnalyzer.new()
|
|
for path in SS2D_VersionTransition.find_files("res://", [ "*.tscn" ]):
|
|
if analyzer.load(path):
|
|
if analyzer.change_shape_node_type(_from_type, _to_type, true):
|
|
_files.append(path)
|
|
|
|
func needs_conversion() -> bool:
|
|
return _files.size() > 0
|
|
|
|
func convert() -> bool:
|
|
var analyzer := TscnAnalyzer.new()
|
|
|
|
for path in _files:
|
|
analyzer.load(path)
|
|
analyzer.change_shape_node_type(_from_type, _to_type)
|
|
|
|
if not analyzer.write():
|
|
return false
|
|
|
|
print("SS2D: Converted scene ", path)
|
|
|
|
return true
|
|
|
|
|
|
class TscnAnalyzer:
|
|
extends RefCounted
|
|
|
|
var _path: String
|
|
var _lines: PackedStringArray
|
|
var _shape_script_ids: PackedStringArray
|
|
var _content_start_line: int # Points to the first line after [ext_resource] section
|
|
|
|
func load(tscn_path: String) -> bool:
|
|
_path = tscn_path
|
|
_shape_script_ids.clear()
|
|
var content := FileAccess.get_file_as_string(tscn_path)
|
|
|
|
if not content:
|
|
_lines.clear()
|
|
_content_start_line = 0
|
|
return false
|
|
|
|
_lines = content.split("\n")
|
|
_content_start_line = _extract_shape_script_ids(_shape_script_ids)
|
|
return true
|
|
|
|
func contains_shapes() -> bool:
|
|
return _shape_script_ids.size() > 0
|
|
|
|
## Writes the internal buffer to the given file. If no file is specified, writes to the loaded file.
|
|
## Returns true on success.
|
|
func write(file_path: String = "") -> bool:
|
|
file_path = file_path if file_path else _path
|
|
|
|
var f := FileAccess.open(file_path, FileAccess.WRITE)
|
|
|
|
if not f:
|
|
push_error("Failed to open file for writing: ", file_path)
|
|
return false
|
|
|
|
f.store_string("\n".join(_lines))
|
|
f.close()
|
|
return true
|
|
|
|
## Changes the node type of shape nodes from the given type to the given type.
|
|
## Returns true if changes were made.
|
|
## If check_only is true, it returns true when conversion is needed, but no modifications are made.
|
|
func change_shape_node_type(from: String, to: String, check_only: bool = false) -> bool:
|
|
if not _shape_script_ids or _content_start_line == -1:
|
|
return false
|
|
|
|
var next_line := _content_start_line
|
|
var re_match_script := RegEx.create_from_string("^script\\s*=\\s*ExtResource\\(\"(%s)\"\\)" % "|".join(_shape_script_ids))
|
|
var re_match_node_type := RegEx.create_from_string("type=\"%s\"" % from)
|
|
var replace_string := "type=\"%s\"" % to
|
|
var dirty := false
|
|
|
|
while true:
|
|
var node_line := _find_node_with_property_re(next_line, re_match_script)
|
|
next_line = node_line + 1
|
|
|
|
if node_line == -1:
|
|
break
|
|
|
|
var replaced := re_match_node_type.sub(_lines[node_line], replace_string)
|
|
|
|
# No change -> nothing to do here
|
|
if replaced == _lines[node_line]:
|
|
continue
|
|
|
|
if check_only:
|
|
return true
|
|
|
|
_lines[node_line] = replaced
|
|
dirty = true
|
|
|
|
return dirty
|
|
|
|
## Examines [ext_resource] entries and updates the given list to include all resource IDs referring
|
|
## to shapes (shape/shape_open/shape_closed.gd).
|
|
## Returns -1 when EOF was reached, otherwise the index of the first non-[ext_resource] line.
|
|
func _extract_shape_script_ids(out_shape_ids: PackedStringArray) -> int:
|
|
var re_ext_resource_path_is_shape := RegEx.create_from_string("path=\"(res://addons/rmsmartshape/shapes/(?:shape|shape_closed|shape_open).gd\")")
|
|
var re_extract_id := RegEx.create_from_string("id=\"([0-9a-z_]+)\"")
|
|
var found_something := false
|
|
|
|
for i in _lines.size():
|
|
var line := _lines[i]
|
|
|
|
if line.begins_with("[ext_resource"):
|
|
if re_ext_resource_path_is_shape.search(line):
|
|
out_shape_ids.append(re_extract_id.search(line).get_string(1))
|
|
found_something = true
|
|
continue
|
|
|
|
# Any other tag like [sub_resource] or [node]. Usually there shouldn't be any intermixed ext_resource tags
|
|
if found_something and line.begins_with("["):
|
|
return i
|
|
|
|
return -1
|
|
|
|
## Searches for property definitions under [node] tags matching the given regex.
|
|
## Returns the line of the [node] tag if a match was found, otherwise -1.
|
|
func _find_node_with_property_re(start_line: int, re: RegEx) -> int:
|
|
var node_line: int = -1
|
|
|
|
for i in range(start_line, _lines.size()):
|
|
var line := _lines[i]
|
|
|
|
if line.begins_with("[node"):
|
|
node_line = i
|
|
elif line.begins_with("["): # There are likely no other tags intermixed but just to be sure
|
|
node_line = -1
|
|
elif node_line != -1:
|
|
if re.search(line):
|
|
return node_line
|
|
|
|
return -1
|
|
|
|
|
|
## Recursively searches for files in the given searchpath.
|
|
## Returns a list of files matching the given glob expressions.
|
|
static func find_files(searchpath: String, globs: PackedStringArray) -> PackedStringArray:
|
|
var root := DirAccess.open(searchpath)
|
|
|
|
if not root:
|
|
push_error("Failed to open directory: ", searchpath)
|
|
|
|
root.include_navigational = false
|
|
root.list_dir_begin()
|
|
|
|
var files: PackedStringArray
|
|
var root_path := root.get_current_dir()
|
|
|
|
while true:
|
|
var fname := root.get_next()
|
|
|
|
if fname.is_empty():
|
|
break
|
|
|
|
var path := root_path.path_join(fname)
|
|
|
|
if root.current_is_dir():
|
|
files.append_array(find_files(path, globs))
|
|
else:
|
|
for expr in globs:
|
|
if fname.match(expr):
|
|
files.append(path)
|
|
break
|
|
|
|
root.list_dir_end()
|
|
return files
|