Add --rebuild-map to enable a TrenchBroom-only level design workflow

This commit is contained in:
Jens Pitkänen 2026-07-23 21:50:10 +03:00
parent 2c9ed4e5da
commit 820a57f609
11 changed files with 201 additions and 118 deletions

View File

@ -0,0 +1,6 @@
@tool
class_name SpawnEntity
extends Node3D
@export var team_index: int = -1
@export var is_ball_spawn: bool = false

View File

@ -0,0 +1 @@
uid://chhq2pb2quwg6

View File

@ -0,0 +1,21 @@
[gd_resource type="Resource" script_class="FuncGodotFGDPointClass" format=3 uid="uid://bqm65x2jpfxho"]
[ext_resource type="Script" uid="uid://d1nwwgcrner8b" path="res://addons/func_godot/src/fgd/func_godot_fgd_point_class_display_descriptor.gd" id="1_0io4j"]
[ext_resource type="Script" uid="uid://cxsqwtsqd8w33" path="res://addons/func_godot/src/fgd/func_godot_fgd_point_class.gd" id="2_nqhon"]
[ext_resource type="Script" uid="uid://chhq2pb2quwg6" path="res://maps/entities/spawn.gd" id="3_nqhon"]
[resource]
script = ExtResource("2_nqhon")
script_class = ExtResource("3_nqhon")
classname = "info_spawn_point"
class_properties = Dictionary[String, Variant]({
"is_ball_spawn": false,
"team_index": -1
})
class_property_descriptions = Dictionary[String, Variant]({
"is_ball_spawn": "If enabled, this entity will be marked as the spawn point for the ball.",
"team_index": "The index of the team whose players will be spawned here. The indices are into the team array in the MapResource file under maps/."
})
auto_apply_to_matching_node_properties = true
node_class = "Node3D"
metadata/_custom_type_script = "uid://cxsqwtsqd8w33"

View File

@ -1,12 +1,13 @@
[gd_resource type="Resource" script_class="FuncGodotFGDFile" format=3 uid="uid://dhq2srgr0vr1m"] [gd_resource type="Resource" script_class="FuncGodotFGDFile" format=3 uid="uid://dhq2srgr0vr1m"]
[ext_resource type="Resource" uid="uid://cb5fm54x4t23d" path="res://maps/meta/worldspawn.tres" id="1_fkacr"] [ext_resource type="Resource" uid="uid://cb5fm54x4t23d" path="res://maps/entities/worldspawn.tres" id="1_fkacr"]
[ext_resource type="Resource" uid="uid://crgpdahjaj" path="res://addons/func_godot/fgd/func_godot_fgd.tres" id="1_j17uj"] [ext_resource type="Resource" uid="uid://crgpdahjaj" path="res://addons/func_godot/fgd/func_godot_fgd.tres" id="1_j17uj"]
[ext_resource type="Script" uid="uid://drlmgulwbjwqu" path="res://addons/func_godot/src/fgd/func_godot_fgd_file.gd" id="2_2w5ia"] [ext_resource type="Script" uid="uid://drlmgulwbjwqu" path="res://addons/func_godot/src/fgd/func_godot_fgd_file.gd" id="2_2w5ia"]
[ext_resource type="Resource" uid="uid://bqm65x2jpfxho" path="res://maps/entities/spawn.tres" id="3_qwe78"]
[resource] [resource]
script = ExtResource("2_2w5ia") script = ExtResource("2_2w5ia")
fgd_name = "QuakeBall" fgd_name = "QuakeBall"
base_fgd_files = Array[Resource]([ExtResource("1_j17uj")]) base_fgd_files = Array[Resource]([ExtResource("1_j17uj")])
entity_definitions = Array[Resource]([ExtResource("1_fkacr")]) entity_definitions = Array[Resource]([ExtResource("1_fkacr"), ExtResource("3_qwe78")])
metadata/_custom_type_script = "uid://drlmgulwbjwqu" metadata/_custom_type_script = "uid://drlmgulwbjwqu"

View File

@ -0,0 +1,60 @@
@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.
func _ready() -> void:
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 map: Map = get_parent()
# Rebuild func_godot_map
var func_godot_maps: Array = map.find_children("*", "FuncGodotMap")
assert(len(func_godot_maps) == 1)
var func_godot_map: FuncGodotMap = func_godot_maps[0]
print("Rebuilding ", func_godot_map.name, "...")
func_godot_map.build()
print(func_godot_map.name, " rebuilt. Registering spawners...")
# Hook up team and ball spawns
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(spawner_count, " spawners registered. Saving scene...")
var packed_scene = PackedScene.new()
packed_scene.pack(get_tree().edited_scene_root)
ResourceSaver.save(packed_scene, get_tree().edited_scene_root.scene_file_path)
print("Scene saved. All rebuilding steps complete!")
return true

View File

@ -0,0 +1 @@
uid://diepa0rcedh5f

View File

@ -1,20 +0,0 @@
extends SceneTree
func _init() -> void:
var args: PackedStringArray = OS.get_cmdline_user_args()
var next_is_map = false
for arg in args:
if next_is_map:
rebuild_map(arg)
return
elif arg == "--map":
next_is_map = true
print("No map provided. (Make sure to include e.g. '-- --map two_towers' after the godot invocation)")
quit(2)
func rebuild_map(name: String) -> void:
var map_scene_path: String = "res://maps/" + name + "/" + name + ".tscn"
var scene: Resource = load(map_scene_path) as PackedScene
var instantiated_scene = scene.instantiate(PackedScene.GEN_EDIT_STATE_MAIN)
print("TODO: Rebuild the map & whatever post-processing, and save this back to disk: ", instantiated_scene)
quit(0)

View File

@ -1 +0,0 @@
uid://2ipxbi1glfsq

View File

@ -151,15 +151,6 @@
} }
// brush 15 // brush 15
{ {
( -256 -128 0 ) ( -256 -127 0 ) ( -256 -128 1 ) str_stonewall4 [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5
( -256 -128 0 ) ( -256 -128 1 ) ( -255 -128 0 ) str_stonewall4 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5
( -256 -128 0 ) ( -255 -128 0 ) ( -256 -127 0 ) str_stonewall4 [ -1 0 0 0 ] [ 0 -1 0 0 ] 0 0.5 0.5
( -192 0 32 ) ( -192 1 32 ) ( -191 0 32 ) str_stonewall4 [ 1 0 0 0 ] [ 0 -1 0 0 ] 180 0.5 0.5
( -192 0 64 ) ( -191 0 64 ) ( -192 0 65 ) str_stonewall4 [ -1 0 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5
( -192 0 64 ) ( -192 0 65 ) ( -192 1 64 ) str_stonewall4 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5
}
// brush 16
{
( 16 -16 32 ) ( 0 -128 32 ) ( 0 -128 0 ) str_stoneflr5 [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5 ( 16 -16 32 ) ( 0 -128 32 ) ( 0 -128 0 ) str_stoneflr5 [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5
( 128 0 0 ) ( 128 0 32 ) ( 16 -16 32 ) str_stoneflr5 [ -1 0 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5 ( 128 0 0 ) ( 128 0 32 ) ( 16 -16 32 ) str_stoneflr5 [ -1 0 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5
( 0 -128 32 ) ( 128 -128 32 ) ( 128 -128 0 ) str_stoneflr5 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5 ( 0 -128 32 ) ( 128 -128 32 ) ( 128 -128 0 ) str_stoneflr5 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5
@ -167,7 +158,7 @@
( 16 -16 32 ) ( 128 0 32 ) ( 128 -128 32 ) str_stoneflr5 [ 1 0 0 0 ] [ 0 -1 0 0 ] 180 0.5 0.5 ( 16 -16 32 ) ( 128 0 32 ) ( 128 -128 32 ) str_stoneflr5 [ 1 0 0 0 ] [ 0 -1 0 0 ] 180 0.5 0.5
( 128 -128 32 ) ( 128 0 32 ) ( 128 0 0 ) str_stoneflr5 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5 ( 128 -128 32 ) ( 128 0 32 ) ( 128 0 0 ) str_stoneflr5 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5
} }
// brush 17 // brush 16
{ {
( -128 128 32 ) ( -128 0 32 ) ( -128 0 0 ) str_stoneflr5 [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5 ( -128 128 32 ) ( -128 0 32 ) ( -128 0 0 ) str_stoneflr5 [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5
( -16 16 0 ) ( 0 128 0 ) ( -128 128 0 ) str_stoneflr5 [ -1 0 0 0 ] [ 0 -1 0 0 ] 0 0.5 0.5 ( -16 16 0 ) ( 0 128 0 ) ( -128 128 0 ) str_stoneflr5 [ -1 0 0 0 ] [ 0 -1 0 0 ] 0 0.5 0.5
@ -176,7 +167,7 @@
( -128 0 32 ) ( -16 16 32 ) ( -16 16 0 ) str_stoneflr5 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5 ( -128 0 32 ) ( -16 16 32 ) ( -16 16 0 ) str_stoneflr5 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5
( -16 16 32 ) ( 0 128 32 ) ( 0 128 0 ) str_stoneflr5 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5 ( -16 16 32 ) ( 0 128 32 ) ( 0 128 0 ) str_stoneflr5 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5
} }
// brush 18 // brush 17
{ {
( 512 -256 256 ) ( 512 -128 0 ) ( 512 -128 256 ) str_stonebrk1 [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5 ( 512 -256 256 ) ( 512 -128 0 ) ( 512 -128 256 ) str_stonebrk1 [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5
( 576 -256 256 ) ( 512 -256 0 ) ( 512 -256 256 ) str_stonebrk1 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5 ( 576 -256 256 ) ( 512 -256 0 ) ( 512 -256 256 ) str_stonebrk1 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5
@ -185,7 +176,7 @@
( 576 -128 256 ) ( 512 -128 0 ) ( 576 -128 0 ) str_stonebrk1 [ -1 0 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5 ( 576 -128 256 ) ( 512 -128 0 ) ( 576 -128 0 ) str_stonebrk1 [ -1 0 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5
( 576 -128 256 ) ( 576 -256 0 ) ( 576 -256 256 ) str_stonebrk1 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5 ( 576 -128 256 ) ( 576 -256 0 ) ( 576 -256 256 ) str_stonebrk1 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5
} }
// brush 19 // brush 18
{ {
( -576 64 256 ) ( -576 192 0 ) ( -576 192 256 ) str_stonebrk1 [ 0 -1 0 128 ] [ 0 0 -1 0 ] 90 0.5 0.5 ( -576 64 256 ) ( -576 192 0 ) ( -576 192 256 ) str_stonebrk1 [ 0 -1 0 128 ] [ 0 0 -1 0 ] 90 0.5 0.5
( -512 64 256 ) ( -576 64 0 ) ( -576 64 256 ) str_stonebrk1 [ 1 0 0 128 ] [ 0 0 -1 0 ] 90 0.5 0.5 ( -512 64 256 ) ( -576 64 0 ) ( -576 64 256 ) str_stonebrk1 [ 1 0 0 128 ] [ 0 0 -1 0 ] 90 0.5 0.5
@ -194,7 +185,7 @@
( -512 192 256 ) ( -576 192 0 ) ( -512 192 0 ) str_stonebrk1 [ -1 0 0 -128 ] [ 0 0 -1 0 ] 90 0.5 0.5 ( -512 192 256 ) ( -576 192 0 ) ( -512 192 0 ) str_stonebrk1 [ -1 0 0 -128 ] [ 0 0 -1 0 ] 90 0.5 0.5
( -512 192 256 ) ( -512 64 0 ) ( -512 64 256 ) str_stonebrk1 [ 0 1 0 -128 ] [ 0 0 -1 0 ] 90 0.5 0.5 ( -512 192 256 ) ( -512 64 0 ) ( -512 64 256 ) str_stonebrk1 [ 0 1 0 -128 ] [ 0 0 -1 0 ] 90 0.5 0.5
} }
// brush 20 // brush 19
{ {
( 384 -256 192 ) ( 384 -320 192 ) ( 384 -256 0 ) str_stonebrk1 [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5 ( 384 -256 192 ) ( 384 -320 192 ) ( 384 -256 0 ) str_stonebrk1 [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5
( 448 -320 0 ) ( 384 -320 0 ) ( 448 -320 192 ) str_stonebrk1 [ -1 -2.220446049250313e-16 0 128 ] [ 0 0 -1 0 ] 0 0.5 0.5 ( 448 -320 0 ) ( 384 -320 0 ) ( 448 -320 192 ) str_stonebrk1 [ -1 -2.220446049250313e-16 0 128 ] [ 0 0 -1 0 ] 0 0.5 0.5
@ -203,7 +194,7 @@
( 384 -256 0 ) ( 448 -256 0 ) ( 384 -256 192 ) clip [ -1 0 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5 ( 384 -256 0 ) ( 448 -256 0 ) ( 384 -256 192 ) clip [ -1 0 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5
( 448 -256 0 ) ( 448 -320 0 ) ( 448 -256 192 ) str_stonebrk1 [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5 ( 448 -256 0 ) ( 448 -320 0 ) ( 448 -256 192 ) str_stonebrk1 [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5
} }
// brush 21 // brush 20
{ {
( 384 -448 0 ) ( 384 -384 0 ) ( 384 -448 192 ) str_stonebrk1 [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5 ( 384 -448 0 ) ( 384 -384 0 ) ( 384 -448 192 ) str_stonebrk1 [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5
( 384 -448 192 ) ( 448 -448 192 ) ( 384 -448 0 ) str_stonebrk1 [ -1 -2.220446049250313e-16 0 128 ] [ 0 0 -1 0 ] 0 0.5 0.5 ( 384 -448 192 ) ( 448 -448 192 ) ( 384 -448 0 ) str_stonebrk1 [ -1 -2.220446049250313e-16 0 128 ] [ 0 0 -1 0 ] 0 0.5 0.5
@ -212,7 +203,7 @@
( 384 -384 192 ) ( 384 -384 0 ) ( 448 -384 192 ) str_stonebrk1 [ 1 -2.220446049250313e-16 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5 ( 384 -384 192 ) ( 384 -384 0 ) ( 448 -384 192 ) str_stonebrk1 [ 1 -2.220446049250313e-16 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5
( 448 -448 192 ) ( 448 -384 192 ) ( 448 -448 0 ) str_stonebrk1 [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5 ( 448 -448 192 ) ( 448 -384 192 ) ( 448 -448 0 ) str_stonebrk1 [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5
} }
// brush 22 // brush 21
{ {
( 384 -384 192 ) ( 384 -384 128 ) ( 384 -320 192 ) str_stonebrk1 [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5 ( 384 -384 192 ) ( 384 -384 128 ) ( 384 -320 192 ) str_stonebrk1 [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5
( 448 -384 192 ) ( 448 -384 128 ) ( 384 -384 192 ) str_wasteland4 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5 ( 448 -384 192 ) ( 448 -384 128 ) ( 384 -384 192 ) str_wasteland4 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5
@ -221,7 +212,7 @@
( 384 -320 192 ) ( 384 -320 128 ) ( 448 -320 192 ) str_wasteland4 [ -1 0 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5 ( 384 -320 192 ) ( 384 -320 128 ) ( 448 -320 192 ) str_wasteland4 [ -1 0 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5
( 448 -320 192 ) ( 448 -320 128 ) ( 448 -384 192 ) str_stonebrk1 [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5 ( 448 -320 192 ) ( 448 -320 128 ) ( 448 -384 192 ) str_stonebrk1 [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 0.5 0.5
} }
// brush 23 // brush 22
{ {
( 416 -384 128 ) ( 416 -320 0 ) ( 416 -320 128 ) clip [ 0 -1 0 0 ] [ 0 0 -1 0 ] 270 0.5 0.5 ( 416 -384 128 ) ( 416 -320 0 ) ( 416 -320 128 ) clip [ 0 -1 0 0 ] [ 0 0 -1 0 ] 270 0.5 0.5
( 480 -384 128 ) ( 416 -384 0 ) ( 416 -384 128 ) clip [ 1 0 0 -64 ] [ 0 0 -1 0 ] 270 0.5 0.5 ( 480 -384 128 ) ( 416 -384 0 ) ( 416 -384 128 ) clip [ 1 0 0 -64 ] [ 0 0 -1 0 ] 270 0.5 0.5
@ -231,3 +222,21 @@
( 448 -320 128 ) ( 448 -384 0 ) ( 448 -384 128 ) clip [ 0 1 0 0 ] [ 0 0 -1 0 ] 180 0.5 0.5 ( 448 -320 128 ) ( 448 -384 0 ) ( 448 -384 128 ) clip [ 0 1 0 0 ] [ 0 0 -1 0 ] 180 0.5 0.5
} }
} }
// entity 1
{
"classname" "info_spawn_point"
"origin" "72 -72 40"
"team_index" "1"
}
// entity 2
{
"classname" "info_spawn_point"
"origin" "-72 56 40"
"team_index" "0"
}
// entity 3
{
"classname" "info_spawn_point"
"origin" "-440 424 168"
"is_ball_spawn" "1"
}

File diff suppressed because one or more lines are too long