extends Node class_name WorldLayout ## Reads the kitchen a scene file authored and describes it as data, so the ## server can respawn it as replicated objects. ## ## The authored nodes are a template, not the live world. Baking them into the ## scene would mean each peer ran its own unsynced copy; spawning them means the ## client receives the server's, through the same path as a mid-session join. ## Directories whose scenes count as world content, and the base type to look for ## in each. Anything instanced from these is picked up automatically — adding a ## new station or item needs no change here. const SOURCES := [ {"dir": "res://Stations/", "type": "StaticBody3D"}, {"dir": "res://Items/", "type": "XRToolsPickable"}, {"dir": "res://Containers/", "type": "XRToolsPickable"}, ] ## Every authored node in the current scene, stations first. ## ## Order matters: stations have to exist before items, so an item spawning on top ## of one lands in a snap zone that is already there. func get_authored_nodes() -> Array[Node]: var found: Array[Node] = [] for source in SOURCES: for node in _instances_of(source["dir"], source["type"]): if not found.has(node): found.append(node) return found func _instances_of(dir: String, type: String) -> Array[Node]: var scenes := [] for file in ResourceLoader.list_directory(dir): scenes.append(dir + file) var found: Array[Node] = [] for node in get_tree().root.find_children("*", type, true, false): if node.scene_file_path in scenes: found.append(node) return found ## Turns authored nodes into spawn descriptions. func describe(nodes: Array[Node]) -> Array[Dictionary]: var data: Array[Dictionary] = [] for node in nodes: data.append(_describe_one(node)) return data func _describe_one(node: Node) -> Dictionary: return { "scene": node.scene_file_path, "name": node.name, # Global, not local: the copies are respawned under one content root, so # an authored node nested inside another (JonScene parents Counter5 under # Counter3) would otherwise land in the wrong place. "xform": node.global_transform, "props": _authored_props(node), } # Only authored configuration — @export vars, the ones the editor exposes. Plain # script variables are live runtime state, and replaying those into a fresh # instance re-runs their setters before the node is in the tree, where any setter # touching an @onready reference blows up. func _authored_props(node: Node) -> Dictionary: var props := {} var script: Script = node.get_script() as Script if not script: return props for prop in script.get_script_property_list(): var prop_name := str(prop["name"]) if not (prop["usage"] & PROPERTY_USAGE_EDITOR) or prop_name.begins_with("_"): continue props[prop_name] = node.get(prop_name) return props