@tool extends Node # This node and script are here to rebuild the .map file and do general updating stuff # when invoked with the --rebuild-map user CLI argument. This is useful for being able # to update the scene with the latest changes to the .map file automatically in a # trenchbroom compile step. The way this works is that this script is a bit of a sleeper agent, # doing nothing most of the time, but if the engine is running in Editor mode and the --rebuild-map # user CLI argument is passed, this script runs the "Build Map" buttons' function for the func_godot # map, and does some other misc. node setup (like hooking up spawn points to the Map), and then # quits. With the headless argument, this allows rebuilding the Godot representation of the map, and # doing menial maintenance work, all from a single CLI command. That TrenchBroom can also invoke, # enabling a level design iteration loop without any (visible) Godot editor in the loop. # # E.g. to rebuild the Two Towers map from CLI, run this from repo root: # godot --headless --editor --path godot res://maps/two_towers/two_towers.tscn -- --rebuild-map # # In TrenchBroom, the following should work as "Parameters" of a "Run Tool" compile step with Godot as the tool: # --headless --editor --path godot res://maps/${MAP_BASE_NAME}/${MAP_BASE_NAME}.tscn -- --rebuild-map # Another step might be useful for actually running the game. enum MapPart { MAP_MAIN, MAP_STATIC } @export var map_part: MapPart = MapPart.MAP_MAIN func _ready() -> void: if not Engine.is_editor_hint(): return var args: PackedStringArray = OS.get_cmdline_user_args() if args.has("--rebuild-map"): var success: bool = rebuild_map() get_tree().quit(0 if success else 1) func rebuild_map() -> bool: var parent: Node = get_parent() if parent != get_tree().edited_scene_root: # This map_rebuilder is likely the static one, but the scene open in the # editor is the main one. Skip! print("[map_rebuilder] Skipping ", MapPart.find_key(map_part), " processing due to not being the currently open scene. (This is expected.)") return true # Ensure the map has actually changed since this scene was last saved var scene_path = get_tree().edited_scene_root.scene_file_path var scene_modified = FileAccess.get_modified_time(scene_path) var map_path = scene_path.replace("_static.scn", ".map").replace(".tscn", ".map") var map_modified = FileAccess.get_modified_time(map_path) if scene_modified > map_modified: print("[map_rebuilder] ", MapPart.find_key(map_part), " is already up to date with the .map file, skipping.") return true # Rebuild func_godot_map if map_part == MapPart.MAP_STATIC: print("[map_rebuilder] Rebuilding FuncGodotMap...") var scene: Node3D = parent var func_godot_maps: Array = scene.find_children("*", "FuncGodotMap") assert(len(func_godot_maps) == 1) var func_godot_map: FuncGodotMap = func_godot_maps[0] func_godot_map.build() print("[map_rebuilder] ", func_godot_map.name, " rebuilt.") # Hook up team and ball spawns if map_part == MapPart.MAP_MAIN: print("[map_rebuilder] Hooking up spawners for the map...") var map: Map = parent map.spawners = [] var spawner_count: int = 0 var spawn_entities: Array = map.find_children("*", "SpawnEntity") for node in spawn_entities: var spawn_entity: SpawnEntity = node if spawn_entity.is_ball_spawn: map.ball_spawner = spawn_entity spawner_count += 1 if spawn_entity.team_index >= 0: while len(map.spawners) <= spawn_entity.team_index: map.spawners.push_back(null) map.spawners[spawn_entity.team_index] = spawn_entity spawner_count += 1 print("[map_rebuilder] ", spawner_count, " spawners registered.") # Save current scene var packed_scene = PackedScene.new() packed_scene.pack(get_tree().edited_scene_root) ResourceSaver.save(packed_scene, scene_path) print("[map_rebuilder] Scene saved.") return true