extends Node const DEFAULT_LIQUID_DEPTH: float = 32.0 var _convex_decomposition_settings: MeshConvexDecompositionSettings func _ready() -> void: _convex_decomposition_settings = MeshConvexDecompositionSettings.new() _convex_decomposition_settings.max_concavity = 0.0 _convex_decomposition_settings.max_convex_hulls = 32 _convex_decomposition_settings.resolution = 100000 func setup(scene: Node) -> Object: #print(scene.get_meta(&'extras')) if scene: var meshes: Array[Mesh] = [] var mats: Array[Material] = [] var nodes_by_unique_name := {} print("===Map preinit for %s===" % scene) print("---Setting up nodes---") _setup_nodes(scene, scene, meshes, mats, nodes_by_unique_name) print("---Applying shaders---") _apply_shaders(meshes, mats) print("---Applying properties---") _apply_properties(scene, scene, nodes_by_unique_name) print("===Map preinit for %s complete===" % scene) #scene.print_tree_pretty() return scene func _setup_nodes( what: Node, root: Node, meshes: Array[Mesh], mats: Array[Material], nodes_by_unique_name: Dictionary ) -> void: for child in what.get_children(): _setup_nodes(child, root, meshes, mats, nodes_by_unique_name) _setup_node(what, root, meshes, mats, nodes_by_unique_name) func _record_mesh_and_mats( what: Node, root: Node, mesh: Mesh, meshes: Array[Mesh], mats: Array[Material] ) -> void: if mesh and not (mesh in meshes): #print(( #"Recording mesh %s and its materials " + #"for use during applying shaders" #) % mesh) meshes.push_back(mesh) for i in mesh.get_surface_count(): var mat := mesh.surface_get_material(i) if mat and not (mat in mats): #print("Recording material: %s" % mat) mats.push_back(mat) var meta := mat.get_meta(&'extras', {}) as Dictionary var known_surfaces := root.get_meta(&'extras', {}).get( root.get_path_to(what), {} ).get(&'surfaces', []) as Array if known_surfaces.size() > i: meta.merge(known_surfaces[i], true) mat.set_meta(&'extras', meta) func _setup_node( what: Node, root: Node, meshes: Array[Mesh], mats: Array[Material], nodes_by_unique_name: Dictionary ) -> void: #print("Setting up node %s" % what) var unique_name := what.name var replacement: Node = null var discard := false var recursive_discard := false var extras := {} var all_extras := root.get_meta(&'extras', {}) as Dictionary var path := root.get_path_to(what) var meta := all_extras.get(path, {}) as Dictionary extras.merge(meta.get(&'mesh', {}), true) extras.merge(meta.get(&'node', {}), true) what.set_meta(&'extras', extras) var node_type := &'' var path_prefix := "" #print("Assessing node category...") if extras.has(&'SpawnGroup'): match extras.SpawnGroup: &'conditional': node_type = &'ConditionalSpawnGroup' &'deferred': node_type = &'DeferredSpawnGroup' &'spawner': node_type = &'Spawner' elif extras.has(&'NPC'): #print("Node is npc: %s" % extras.NPC) node_type = &'NPC' path_prefix = "res://characters/npcs/%s/%s" % [extras.NPC, extras.NPC] elif extras.has(&'Enemy'): #print("Node is enemy: %s" % extras.Enemy) node_type = &'Enemy' path_prefix = "res://characters/enemies/%s/%s" % [ extras.Enemy, extras.Enemy ] elif extras.has(&'Liquid'): #print("Node is liquid: %s" % extras.Liquid) node_type = &'Liquid' path_prefix = "res://zones/Liquid/descriptors/%s" % extras.Liquid elif extras.has(&'Object'): #print("Node is game-object: %s" % extras.Object) node_type = &'Object' path_prefix = "res://objects/%s/%s" % [extras.Object, extras.Object] elif extras.has(&'Path'): #print("Node is path: %s" % extras.Path) node_type = &'Path' elif extras.has(&'VisualLiquid'): #print("Node is visual liquid: %s" % extras.VisualLiquid) node_type = &'VisualLiquid' path_prefix = "res://zones/Liquid/descriptors/%s" % extras.VisualLiquid elif extras.has(&'Zone'): #print("Node is zone: %s" % extras.Zone) node_type = &'Zone' path_prefix = "res://zones/%s/%s" % [extras.Zone, extras.Zone] elif extras.has(&'Intangible') and not extras.Intangible and ( what is Node3D and not (what is MeshInstance3D) ): #print("Node is invisible wall") node_type = &'InvisibleWall' else: #print("Node is uncategorized; no special setup needed") pass if not node_type.is_empty(): print("Setting up node %s as %s%s" % [ root.get_path_to(what), node_type, (":%s" % extras.get(node_type)) if extras.has(node_type) else "" ]) var script_path: String = path_prefix + ".gd" var scene_path: String = path_prefix + ".tscn" var resource_path: String = path_prefix + ".tres" var instantiated_from_script := false #print("Doing node-category-specific setup:") match node_type: &'': #print("None needed; to reiterate, node is uncategorized") pass &'ConditionalSpawnGroup': #print("Instantiating ConditionalSpawnGroup") replacement = ConditionalSpawnGroup.new() #print("Instantiated: %s" % replacement) &'DeferredSpawnGroup': replacement = DeferredSpawnGroup.new() &'InvisibleWall': #print("Instantiating StaticBody3D") replacement = StaticBody3D.new() #print("Instantiated: %s" % replacement) &'Liquid': #print("Instantiating Liquid node") replacement = load("res://zones/Liquid/Liquid.tscn").instantiate() #print("Instantiated: %s" % replacement) #print( #"Trying to load LiquidDescriptor from %s" % #resource_path #) if ResourceLoader.exists(resource_path): replacement.descriptor = load(resource_path) #print( #"Loaded LiquidDescriptor: %s" % #replacement.descriptor #) else: #print("!!!LiquidDescriptor does not exist!!!") push_error( "LiquidDescriptor @ %s not found" % resource_path ) &'Path': if what is MeshInstance3D: #print("Node stands in for a Curve3D, " + #"which is a Resource; " + #"actual node will be discarded") discard = true #print("Instantiating Curve3D") var curve := Curve3D.new() # Might try to switch to Hobby algorithm later, not sure #print("Building curve from vertices of surface 0") for vertex in ( what.mesh.surface_get_arrays(0)[Mesh.ARRAY_VERTEX] ): #print("Adding vertex %s" % vertex) curve.add_point(vertex) #print("Storing curve to extras under &'%s'" % extras.Path) extras[extras.Path] = curve else: #print("!!!Node is path, but is not MeshInstance3D!!!") push_error(( "Node marked to become a path " + "is not a MeshInstance3D: %s" ) % what) &'Spawner': replacement = Spawner.new() &'VisualLiquid': #print("Instantiating VisualLiquid node") replacement = load("res://zones/Liquid/VisualLiquid.gd").new() #print( #"Trying to load LiquidDescriptor from %s" % #resource_path #) if ResourceLoader.exists(resource_path): replacement.descriptor = load(resource_path) #print( #"Loaded LiquidDescriptor: %s" % #replacement.descriptor #) else: #print("!!!LiquidDescriptor does not exist!!!") push_error( "LiquidDescriptor @ %s not found" % resource_path ) assert(what is MeshInstance3D) #print("Grabbing mesh from original") replacement.mesh = what.mesh #print("Attaching liquid shader as override") replacement.set_surface_override_material(0, preload( "res://zones/Liquid/LiquidMaterial.tres" )) _: if ResourceLoader.exists(scene_path, &'PackedScene'): #print("Instantiating %s node from scene %s" % [ #node_type, scene_path #]) replacement = load(scene_path).instantiate() #print("Result: %s" % replacement) elif ResourceLoader.exists(script_path, &'GDScript'): #print("Instantiating %s node from script %s" % [ #node_type, script_path #]) replacement = load(script_path).new() #print("Result: %s" % replacement) instantiated_from_script = true else: #print("!!!Node type does not exist: %s %s!!!" % [ #node_type, extras[node_type] #]) push_error("No such %s: %s (what: %s)" % [ node_type, extras[node_type], what ]) #print("Node-category-specific setup done") if discard: #print("Node will be discarded") var parent := what.get_parent() if not recursive_discard: #print("Propagating children up to parent") for child in what.get_children(): #print("Removing child %s" % child) what.remove_child(child) #print("Adding to parent") what.get_parent().add_child(child) #print("Removing discarded node from tree") parent.remove_child(what) #print("Queuing discarded node to be freed") what.queue_free() else: if replacement: #print("Adding instantiated node %s to tree" % replacement) if ( node_type == &'ConditionalSpawnGroup' or node_type == &'DeferredSpawnGroup' or node_type == &'Spawner' ): replacement.root = what var parent := what.get_parent() parent.remove_child(what) parent.add_child(replacement) elif node_type == &'Zone' and what is MeshInstance3D: #print(( #"Node is a zone, and original was a MeshInstance3D " + #"(%s); therefore, the instantiated zone belongs " + #"under the original MeshInstance3D as a child, " + #"rather than in its place as a replacement; " + #"adding as child" #) % what) replacement.name = extras.Zone what.add_child(replacement) elif node_type == &'Liquid' and what is MeshInstance3D: #print(( #"Node is a liquid, " + #"and original was a MeshInstance3D " + #"(%s); therefore, the instantiated liquid belongs " + #"under the original MeshInstance3D as a child, " + #"rather than in its place as a replacement; " + #"adding as child" #) % what) replacement.name = &'Liquid' what.add_child(replacement) #print("Generating collision meshes") for collider in PlaneConvexDecompose.from( what.mesh, extras.get(&'Depth', DEFAULT_LIQUID_DEPTH) ): replacement.add_child(collider) collider.owner = root #print("Generating collision meshes " + #"from the original MeshInstance3D " + #"for use for the instantiated liquid") #var colliders := _get_multiple_convex_collisions( #what, _convex_decomposition_settings #) #print("Adding collision meshes as children") #for collider in colliders: #print("Adding collision mesh %s" % collider) #replacement.add_child(collider) #print("Generating collision trimesh") #var collider := _get_trimesh(what) #replacement.add_child(collider) elif ( node_type == &'Object' and instantiated_from_script and (what is MeshInstance3D) ): #print(( #"Node is a game-object instantiated from a script, " + #"not a scene, and original was a MeshInstance3D " + #"(%s); therefore, the original MeshInstance3D " + #"should be preserved under its replacement " + #"as a child, and also used to generate " + #"collision meshes" #) % what) #print("Recording original's mesh and materials") _record_mesh_and_mats(what, root, what.mesh, meshes, mats) #print("Replacing original with replacement") replacement.name = what.name _replace_node_maybe_with_scene_instance( what, replacement ) #print("Adding original as child to replacement") what.name = &'MeshInstance3D' replacement.add_child(what) #print("Generating collision meshes") var colliders := _get_multiple_convex_collisions( what, _convex_decomposition_settings ) #print("Adding collision meshes as children") for collider in colliders: #print("Adding collision mesh %s" % collider) replacement.add_child(collider) else: #print(( #"Replacing original node (%s) " + #"with instantiated replacement (%s)" #) % [what, replacement]) replacement.name = what.name _replace_node_maybe_with_scene_instance( what, replacement ) #print("Queuing original to be freed") what.queue_free() if replacement is Node3D and what is Node3D and not ( what.get_parent() == replacement ) and not ( replacement.get_parent() == what ): #print("Both instantiated node and original are Node3D; " + #"ergo, copying transform") replacement.transform = what.transform elif replacement is Node2D and what is Node2D and not ( what.get_parent() == replacement ) and not ( replacement.get_parent() == what ): #print("Both instantiated node and original are Node2D; " + #"ergo, copying transform (%s)" % what.transform) replacement.transform = what.transform if node_type == &'Zone' or (( node_type == &'Liquid' or node_type == &'InvisibleWall' ) and not (what is MeshInstance3D)): #print("Instantiated node still needs a collision mesh, " + #"but building one from the original node " + #"is either not appropriate (if instantiated node " + #"is a zone) or not possible (if original node " + #"is not a MeshInstance3D); will use a BoxShape3D") #print("Instantiating CollisionShape3D") var collision_shape := CollisionShape3D.new() collision_shape.name = &'CollisionShape3D' #print("Instantiating BoxShape3D resource") collision_shape.shape = BoxShape3D.new() #print("Setting box size from node scale (2.0*%s)" % #what.scale) collision_shape.shape.size = 2.0*what.scale #print("Setting node scale to identity") replacement.scale = Vector3.ONE #print("Adding collision mesh as child") replacement.add_child(collision_shape) #print("Setting collision mesh's transform to identity") collision_shape.transform = Transform3D.IDENTITY #print("Copying extras to replacement") replacement.set_meta(&'extras', extras) what = replacement if what is MeshInstance3D: #print("Node is MeshInstance3D; recording mesh and materials") _record_mesh_and_mats(what, root, what.mesh, meshes, mats) if ( node_type != &'VisualLiquid' and not extras.get(&'Intangible', false) ): #print("Node is not intangible; creating collision mesh") what.create_trimesh_collision() #print("Recording node under its unique name") nodes_by_unique_name[unique_name] = what func _get_multiple_convex_collisions( what: MeshInstance3D, params: MeshConvexDecompositionSettings = null ) -> Array[CollisionShape3D]: #print("Creating collision meshes from node %s") var children_before := what.get_children() what.create_multiple_convex_collisions(params) var children_after := what.get_children() #print("Searching children to find result of operation") var staticbody: StaticBody3D for child in children_after: if not (child in children_before): #print("Result found: %s" % child) assert(child is StaticBody3D and not staticbody) staticbody = child #print("Removing resulting staticbody from tree") what.remove_child(staticbody) #print("Collecting collision meshes from staticbody") var colliders: Array[CollisionShape3D] = [] for collider in staticbody.get_children(): assert(collider is CollisionShape3D) #print("Removing collision mesh %s from staticbody" % collider) staticbody.remove_child(collider) #print("Queuing up collision mesh") colliders.push_back(collider) #print("Queuing staticbody to be freed") staticbody.queue_free() return colliders func _get_trimesh(what: MeshInstance3D) -> CollisionShape3D: #print("Creating collision mesh from node %s") var children_before := what.get_children() what.create_trimesh_collision() var children_after := what.get_children() #print("Searching children to find result of operation") var staticbody: StaticBody3D for child in children_after: if not (child in children_before): #print("Result found: %s" % child) assert(child is StaticBody3D and not staticbody) staticbody = child #print("Removing resulting staticbody from tree") what.remove_child(staticbody) #print("Collecting collision meshes from staticbody") var colliders: Array[CollisionShape3D] = [] for collider in staticbody.get_children(): assert(collider is CollisionShape3D) #print("Removing collision mesh %s from staticbody" % collider) staticbody.remove_child(collider) #print("Queuing up collision mesh") colliders.push_back(collider) #print("Queuing staticbody to be freed") staticbody.queue_free() assert(colliders.size() == 1) return colliders[0] func _apply_shaders(meshes: Array[Mesh], mats: Array[Material]) -> void: #print("Building array of ShaderMaterial replacements " + #"for original materials") var shadmats: Array[ShaderMaterial] = [] var shaders := {} for mat in mats: #print("Building ShaderMaterial to replace %s" % mat) var extras := {} if mat.has_meta(&'extras'): extras = mat.get_meta(&'extras') var shadmat := ShaderMaterial.new() shadmats.push_back(shadmat) if extras.has(&'Shader'): #print("Material says to use shader &'%s'; " + # "loading" % extras.Shader) shadmat.shader = load("res://vfx/%s.gdshader" % extras.Shader) else: #print("Material does not specify a shader; will use &'basic'") shadmat.shader = preload("res://vfx/basic.gdshader") shaders[ StringName(extras.Shader) if extras.has(&'Shader') else &'basic' ] = true assert(mat is BaseMaterial3D) #print(( #"Setting shader parameter &'image' " + #"to original material's texture (%s)" #) % mat.albedo_texture) shadmat.set_shader_parameter(&'image', mat.albedo_texture) for k in extras: if k == k.to_lower(): #print(( #"Setting shader parameter &'%s' " + #"to value specified in original material's extras: %s" #) % [k, extras[k]]) shadmat.set_shader_parameter(k, extras[k]) print("Loaded shaders: %s" % (", ".join(shaders.keys()))) #print("Replacing meshes' original materials with ShaderMaterials") for mesh in meshes: #print("Replacing materials in mesh %s" % mesh) for i in mesh.get_surface_count(): #print("Replacing material for surface %d" % i) var j: int = mats.find(mesh.surface_get_material(i)) if j >= 0: mesh.surface_set_material(i, shadmats[j]) func _apply_properties( what: Node, root: Node, nodes_by_unique_name: Dictionary ) -> void: for child in what.get_children(): _apply_properties(child, root, nodes_by_unique_name) if ( (what is ConditionalSpawnGroup) or (what is DeferredSpawnGroup) or (what is Spawner) ): _apply_properties(what.root, what.root, nodes_by_unique_name) _apply_properties_to_node(what, root, nodes_by_unique_name) func _apply_properties_to_node( what: Node, root: Node, nodes_by_unique_name: Dictionary ) -> void: #print("Setting properties for node %s from its extras" % what) var extras := what.get_meta(&'extras', {}) as Dictionary for key in extras: for descriptor in what.get_property_list(): if descriptor.name.capitalize() == key.capitalize(): #print("Interpreting value of extra &'%s'" % key) var value: Variant = _interpret_property_value( descriptor, extras[key], what, root, nodes_by_unique_name ) #print("Value interpreted as %s" % value) print("Setting property: $'%s'.%s = %s" % [ root.get_path_to(what), key, value ]) what.set(key, value) break func _interpret_property_value( descriptor: Dictionary, value: Variant, what: Node, root: Node, nodes_by_unique_name: Dictionary ) -> Variant: #print("First, parsing property hint string '%s'" % descriptor.hint_string) var hint_args: PackedStringArray = descriptor.hint_string.split(',') var hint_arg_args: Array[PackedStringArray] = [] for i in hint_args.size(): var arg_args := hint_args[i].split(':') hint_args[i] = arg_args[0] arg_args.remove_at(0) hint_arg_args.push_back(arg_args) #print("Checking property type") match descriptor.type: TYPE_NIL: #print("Nil property can only be null") return null TYPE_BOOL: #print("Property is bool; double-negating extra") return not not value TYPE_INT: match descriptor.hint: PROPERTY_HINT_ENUM, PROPERTY_HINT_ENUM_SUGGESTION: #print("Property is enum") if value is String: #print("Extra is string; searching enum") var i = hint_args.find(value.capitalize()) if i >= 0 and hint_arg_args[i].size() > 0: #print( #"Found enum value corresponding to given string" #) return hint_arg_args[i][0] else: #print("Could not find enum value " + #"corresponding to given string; defaulting to -1") return -1 else: #print("Extra is not string; skipping enum search " + #"and using directly") return int(value) _: #print("Property is int") return int(value) TYPE_FLOAT: #print("Property is float") return float(value) TYPE_STRING: #print("Property is string") return String(value) TYPE_VECTOR2: #print("Property is Vector2; ergo, " + #"assuming extra is 2-element float array") return Vector2(value[0], value[1]) TYPE_VECTOR2I: #print("Property is Vector2i; ergo, " + #"assuming extra is 2-element int array") return Vector2i(value[0], value[1]) TYPE_RECT2: #print("Property is Rect2; ergo, " + #"assuming extra is 2-element float array") return Rect2(value[0], value[1]) TYPE_RECT2I: #print("Property is Rect2i; ergo, " + #"assuming extra is 2-element int array") return Rect2i(value[0], value[1]) TYPE_VECTOR3: #print("Property is Vector3; ergo, " + #"assuming extra is 3-element float array") return Vector3(value[0], value[1], value[2]) TYPE_VECTOR3I: #print("Property is Vector3i; ergo, " + #"assuming extra is 3-element int array") return Vector3i(value[0], value[1], value[2]) TYPE_TRANSFORM2D: #print("Property is Transform2D; ergo, " + #"assuming extra is 6-element float array") return Transform2D( value[0], Vector2(value[1], value[2]), value[3], Vector2(value[4], value[5]) ) TYPE_VECTOR4: #print("Property is Vector4; ergo, " + #"assuming extra is 4-element float array") return Vector4(value[0], value[1], value[2], value[3]) TYPE_VECTOR4I: #print("Property is Vector4i; ergo, " + #"assuming extra is 4-element int array") return Vector4i(value[0], value[1], value[2], value[3]) TYPE_PLANE: #print("Property is Plane; ergo, " + #"assuming extra is float array with 3, 4, 6, or 9 elements") match value.size(): 3: #print("3 elements; ergo, using Plane(Vector3)") return Plane(Vector3(value[0], value[1], value[2])) 4: #print("4 elements; ergo, using Plane(Vector3, float)") return Plane( Vector3(value[0], value[1], value[2]), value[3] ) 6: #print("6 elements; ergo, using Plane(Vector3, Vector3)") return Plane( Vector3(value[0], value[1], value[2]), Vector3(value[3], value[4], value[5]) ) 9: #print("9 elements; ergo, " + #"using Plane(Vector3, Vector3, Vector3)") return Plane( Vector3(value[0], value[1], value[2]), Vector3(value[3], value[4], value[5]), Vector3(value[6], value[7], value[8]) ) _: #print(( #"!!!Wrong number of elements (%d); " + #"ergo, using Plane()!!!" #) % value.size()) push_error(( "Arguments %s are invalid " + "for constructing Plane" ) % value) return Plane() TYPE_QUATERNION: #print("Property is Quaternion; ergo, " + #"assuming extra is 4-element float array") return Quaternion( value[0], value[1], value[2], value[3] ) TYPE_AABB: #print("Property is AABB; ergo, " + #"assuming extra is 6-element float array") return AABB( Vector3(value[0], value[1], value[2]), Vector3(value[3], value[4], value[5]) ) TYPE_BASIS: #print("Property is Basis; ergo, " + #"assuming extra is 9-element float array") return Basis( Vector3(value[0], value[1], value[2]), Vector3(value[3], value[4], value[5]), Vector3(value[6], value[7], value[8]) ) TYPE_TRANSFORM3D: #print("Property is Transform3D; ergo, " + #"assuming extra is 12-element float array") return Transform3D( Basis( Vector3(value[0], value[1], value[2]), Vector3(value[3], value[4], value[5]), Vector3(value[6], value[7], value[8]) ), Vector3(value[9], value[10], value[11]) ) TYPE_PROJECTION: #print("Property is Projection; ergo, " + #"assuming extra is float array with 12 or 16 elements") match value.size(): 12: #print("12 elements; ergo, " + #"using Projection(Transform3D)") return Projection(Transform3D( Basis( Vector3(value[0], value[1], value[2]), Vector3(value[3], value[4], value[5]), Vector3(value[6], value[7], value[8]) ), Vector3(value[9], value[10], value[11]) )) 16: #print("16 elements; ergo, " + #"using Projection(" + #"Vector4, Vector4, Vector4, Vector4" + #")") return Projection( Vector4(value[0], value[1], value[2], value[3]), Vector4(value[4], value[5], value[6], value[7]), Vector4(value[8], value[9], value[10], value[11]), Vector4(value[12], value[13], value[14], value[15]) ) _: #print(( #"!!!Wrong number of elements (%d); " + #"ergo, using Projection()!!!" #) % value.size()) push_error(( "Arguments %s are invalid " + "for constructing Projection" ) % value) return Projection() TYPE_COLOR: #print("Property is Color") if value is String: #print("Extra is String; parsing as Color") return Color(value) else: #print("Extra is not String; ergo, " + #"assuming float array with 3 or 4 elements") match value.size(): 3: #print("3 elements; ergo, RGB") return Color(value[0], value[1], value[2]) 4: #print("4 elements; ergo, RGBA") return Color(value[0], value[1], value[2], value[3]) _: #print(( #"!!!Wrong number of elements (%d); " + #"ergo, using Color.WHITE!!!" #) % value.size()) push_error(( "Arguments %s are invalid " + "for constructing Color" ) % value) return Color.WHITE TYPE_STRING_NAME: #print("Property is StringName; interning extra") return StringName(String(value)) TYPE_NODE_PATH: #print("Property is NodePath; parsing extra") return NodePath(String(value)) TYPE_RID: #print("Property is RID; " + #"interpreting extra as resource path (without res://)") return load("res://" + String(value)).get_rid() TYPE_OBJECT: match descriptor.hint: PROPERTY_HINT_RESOURCE_TYPE: #print("Property is Resource") # special case for assigning resources from discarded nodes if value is Resource: #print("Extra is Resource, likely built " + #"from discarded node during node setup phase; " + #"using unmodified") return value else: #print("Interpreting extra as resource path " + #"(without res://)") return load("res://" + String(value)) PROPERTY_HINT_NODE_TYPE: #print("Property is Node") if nodes_by_unique_name.has(value): #print("Extra refers to a node's unique name " + #"as defined in the glTF data; " + #"using that node") return nodes_by_unique_name.get(value) elif String(value).begins_with('/'): #print("Extra begins with /; " + #"interpreting as absolute node path") return root.get_node(value) else: #print("Interpreting extra as node path") return what.get_node(value) _: #print("Property is Object; interpreting extra " + #"as name usable with ClassDB") return ClassDB.instantiate(StringName(String(value))) TYPE_CALLABLE: #print("Property is callable; " + #"interpreting extra as method reference (:-style)") #print("Parsing method reference for receiver and method name") var parts := String(value).split(':') #print("Parse result: %s" % parts) if parts.size() == 1: #print("No receiver; assuming implicit self") return Callable(what, parts[0]) elif nodes_by_unique_name.has(parts[0]): #print("Receiver refers to a node's unique name " + #"as defined in the glTF data; " + #"using that node") return Callable(nodes_by_unique_name.get(parts[0]), parts[1]) elif String(parts[0]).begins_with('/'): #print("Receiver begins with /; " + #"interpreting as absolute node path") return Callable(root.get_node(parts[0]), parts[1]) else: #print("Interpreting receiver as node path") return Callable(what.get_node(parts[0]), parts[1]) TYPE_SIGNAL: #print("Property is signal; " + #"interpreting extra as signal reference (:-style)") #print("Parsing signal reference for emitter and signal name") var parts := String(value).split(':') #print("Parse result: %s" % parts) if parts.size() == 1: #print("No emitter; assuming implicit self") return what.get(parts[0]) elif nodes_by_unique_name.has(parts[0]): #print("Emitter refers to a node's unique name " + #"as defined in the glTF data; " + #"using that node") return nodes_by_unique_name.get(parts[0]).get(parts[1]) elif String(parts[0]).begins_with('/'): #print("Emitter begins with /; " + #"interpreting as absolute node path") return root.get_node(parts[0]).get(parts[1]) else: #print("Interpreting emitter as node path") return what.get_node(parts[0]).get(parts[1]) TYPE_DICTIONARY: #print("Property is Dictionary; interpreting extra as JSON") return JSON.parse_string(value) TYPE_ARRAY: #print("Property is array; assuming extra is array") return value TYPE_PACKED_BYTE_ARRAY: #print("Property is PackedByteArray; assuming extra is int array") return PackedByteArray(value) TYPE_PACKED_INT32_ARRAY: #print("Property is PackedInt32Array; assuming extra is int array") return PackedInt32Array(value) TYPE_PACKED_INT64_ARRAY: #print("Property is PackedInt64Array; assuming extra is int array") return PackedInt64Array(value) TYPE_PACKED_FLOAT32_ARRAY: #print("Property is PackedFloat32Array; " + #"assuming extra is float array") return PackedFloat32Array(value) TYPE_PACKED_FLOAT64_ARRAY: #print("Property is PackedFloat64Array; " + #"assuming extra is float array") return PackedFloat64Array(value) TYPE_PACKED_STRING_ARRAY: #print("Property is PackedStringArray; " + #"treating extra as String containing comma-separated tokens") return PackedStringArray(String(value).split(',')) TYPE_PACKED_VECTOR2_ARRAY: #print("Property is PackedVector2Array; ergo, " + #"assuming extra is float array of size divisible by 2") var vecs: Array[Vector2] = [] var current := Vector2.ZERO var current_i: int = 0 for num in value: current[current_i] = value current_i += 1 if current_i >= 2: current_i = 0 #print("Assembled %s" % current) vecs.push_back(current) return PackedVector2Array(vecs) TYPE_PACKED_VECTOR3_ARRAY: #print("Property is PackedVector3Array; ergo, " + #"assuming extra is float array of size divisible by 3") var vecs: Array[Vector2] = [] var current := Vector3.ZERO var current_i: int = 0 for num in value: current[current_i] = value current_i += 1 if current_i >= 3: current_i = 0 #print("Assembled %s" % current) vecs.push_back(current) return PackedVector3Array(vecs) TYPE_PACKED_COLOR_ARRAY: #print("Property is PackedColorArray") var colors: Array[Color] = [] if value is String: #print("Extra is string; parsing as comma-separated tokens") for token in value.split(','): #print("Parsing token '%s' as a Color" % token) colors.push_back(Color(token)) else: #print("Assuming extra is float array of size divisible by 4") var current := Color.WHITE var current_i: int = 0 for num in value: current[current_i] = value current_i += 1 if current_i >= 4: current_i = 0 #print("Assembled %s" % current) colors.push_back(current) return PackedColorArray(colors) _: #print("Property is of unknown or unspecified type; " + #"returning extra unmodified") return value func _replace_node_maybe_with_scene_instance(x: Node, y: Node) -> void: var scene_path := y.scene_file_path x.replace_by(y, true) y.scene_file_path = scene_path