skullball/godot/maps/meta/map_rebuilder.gd

88 lines
3.8 KiB
GDScript

@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