Updated func_godot

This commit is contained in:
MaddoScientisto 2025-12-28 22:53:18 +01:00
commit 01a852de9b
170 changed files with 1705 additions and 2296 deletions

View file

@ -1,5 +1,5 @@
@icon("res://addons/func_godot/icons/icon_godot_ranger.svg")
class_name FuncGodotFGDEntityClass extends Resource
@abstract class_name FuncGodotFGDEntityClass extends Resource
## Entity definition template. WARNING! Not to be used directly! Use [FuncGodotFGDBaseClass], [FuncGodotFGDSolidClass], or [FuncGodotFGDPointClass] instead.
##
## Entity definition template. It holds all of the common entity class properties shared between [FuncGodotFGDBaseClass], [FuncGodotFGDSolidClass], or [FuncGodotFGDPointClass].
@ -28,24 +28,24 @@ var prefix: String = ""
## Key value pair properties that will appear in the map editor. After building the [FuncGodotMap] in Godot, these properties will be added to a [Dictionary]
## that gets applied to the generated node, as long as that node is a tool script with an exported `func_godot_properties` Dictionary.
@export var class_properties : Dictionary = {}
@export var class_properties : Dictionary[String, Variant] = {}
## Map editor descriptions for previously defined key value pair properties. Optional but recommended.
@export var class_property_descriptions : Dictionary = {}
@export var class_property_descriptions : Dictionary[String, Variant] = {}
## Automatically applies entity class properties to matching properties in the generated node.
## When using this feature, class properties need to be the correct type or you may run into errors on map build.
@export var auto_apply_to_matching_node_properties : bool = false
## Appearance properties for the map editor. See the Valve Developer Wiki and TrenchBroom documentation for more information.
@export var meta_properties : Dictionary = {
@export var meta_properties : Dictionary[String, Variant] = {
"size": AABB(Vector3(-8, -8, -8), Vector3(8, 8, 8)),
"color": Color(0.8, 0.8, 0.8)
}
@export_group("Node Generation")
## Node to generate on map build. This can be a built-in Godot class, a GDScript class, or a GDExtension class.
## Node to generate on map build. This can be a built-in Godot class, a Script class, or a GDExtension class.
## For Point Class entities that use Scene File instantiation leave this blank.
@export var node_class := ""
@ -54,6 +54,9 @@ var prefix: String = ""
## Nodes will be named `"entity_" + name_property`. An entity's name should be unique, otherwise you may run into unexpected behavior.
@export var name_property := ""
## Optional array of node groups to add the generated node to.
@export var node_groups : Array[String] = []
## Parses the definition and outputs it into the FGD format.
func build_def_text(target_editor: FuncGodotFGDFile.FuncGodotTargetMapEditors = FuncGodotFGDFile.FuncGodotTargetMapEditors.TRENCHBROOM) -> String:
# Class prefix
@ -231,3 +234,17 @@ func build_def_text(target_editor: FuncGodotFGDFile.FuncGodotTargetMapEditors =
res += "]" + FuncGodotUtil.newline()
return res
func retrieve_all_class_properties(properties: Dictionary[String, Variant] = {}) -> Dictionary[String, Variant]:
for key in class_properties.keys():
properties[key] = class_properties[key]
for b in base_classes:
properties = b.retrieve_all_class_properties(properties)
return properties
func retrieve_all_class_property_descriptions(descriptions: Dictionary[String, Variant] = {}) -> Dictionary[String, Variant]:
for key in class_property_descriptions.keys():
descriptions[key] = class_property_descriptions[key]
for b in base_classes:
descriptions = b.retrieve_all_class_property_descriptions(descriptions)
return descriptions

View file

@ -23,28 +23,25 @@ func export_button() -> void:
do_export_file(target_map_editor)
func do_export_file(target_editor: FuncGodotTargetMapEditors = FuncGodotTargetMapEditors.TRENCHBROOM, fgd_output_folder: String = "") -> void:
if not Engine.is_editor_hint():
return
if fgd_output_folder.is_empty():
fgd_output_folder = FuncGodotLocalConfig.get_setting(FuncGodotLocalConfig.PROPERTY.FGD_OUTPUT_FOLDER) as String
if fgd_output_folder.is_empty():
print("Skipping export: No game config folder")
printerr("Skipping export: No game config folder")
return
if fgd_name == "":
print("Skipping export: Empty FGD name")
printerr("Skipping export: Empty FGD name")
if not DirAccess.dir_exists_absolute(fgd_output_folder):
if DirAccess.make_dir_recursive_absolute(fgd_output_folder) != OK:
print("Skipping export: Failed to create directory")
printerr("Skipping export: Failed to create directory")
return
var fgd_file = fgd_output_folder.path_join(fgd_name + ".fgd")
var file_obj := FileAccess.open(fgd_file, FileAccess.WRITE)
if not file_obj:
print("Failed to open file for writing: ", fgd_file)
printerr("Failed to open file for writing: ", fgd_file)
return
print("Exporting FGD to ", fgd_file)
@ -76,6 +73,9 @@ func do_export_file(target_editor: FuncGodotTargetMapEditors = FuncGodotTargetMa
## Array of resources that inherit from [FuncGodotFGDEntityClass]. This array defines the entities that will be added to the exported FGD file and the nodes that will be generated in a [FuncGodotMap].
@export var entity_definitions: Array[Resource] = []
## Toggles whether [FuncGodotFGDModelPointClass] resources will generate models from their [PackedScene] files.
@export var generate_model_point_class_models: bool = true
func build_class_text(target_editor: FuncGodotTargetMapEditors = FuncGodotTargetMapEditors.TRENCHBROOM) -> String:
var res : String = ""
@ -91,6 +91,8 @@ func build_class_text(target_editor: FuncGodotTargetMapEditors = FuncGodotTarget
continue
if ent.func_godot_internal:
continue
if ent is FuncGodotFGDModelPointClass:
ent._model_generation_enabled = generate_model_point_class_models
var ent_text = ent.build_def_text(target_editor)
res += ent_text
@ -127,9 +129,9 @@ func get_entity_definitions() -> Dictionary[String, FuncGodotFGDEntityClass]:
if ent is FuncGodotFGDPointClass or ent is FuncGodotFGDSolidClass:
var entity_def = ent.duplicate()
var meta_properties := {}
var class_properties := {}
var class_property_descriptions := {}
var meta_properties: Dictionary[String, Variant] = {}
var class_properties: Dictionary[String, Variant] = {}
var class_property_descriptions: Dictionary[String, Variant] = {}
for base_class in _generate_base_class_list(entity_def):
for meta_property in base_class.meta_properties:

View file

@ -29,6 +29,8 @@ enum TargetMapEditor {
## Creates a .gdignore file in the model export folder to prevent Godot importing the display models. Only needs to be generated once.
@export_tool_button("Generate GD Ignore File", "FileAccess") var generate_gd_ignore_file : Callable = _generate_gd_ignore_file
var _model_generation_enabled: bool = false
func _generate_gd_ignore_file() -> void:
if Engine.is_editor_hint():
var path: String = _get_game_path().path_join(_get_model_folder())
@ -45,7 +47,9 @@ func _generate_gd_ignore_file() -> void:
## Builds and saves the display model into the specified destination, then parses the definition and outputs it into the FGD format.
func build_def_text(target_editor: FuncGodotFGDFile.FuncGodotTargetMapEditors = FuncGodotFGDFile.FuncGodotTargetMapEditors.TRENCHBROOM) -> String:
_generate_model()
if _model_generation_enabled:
_generate_model()
_model_generation_enabled = false
return super()
func _generate_model() -> void:
@ -65,7 +69,10 @@ func _generate_model() -> void:
if target_map_editor == TargetMapEditor.TRENCHBROOM:
const model_key: String = "model"
if scale_expression.is_empty():
meta_properties[model_key] = '"%s"' % _get_local_path()
meta_properties[model_key] = '{"path": "%s", "scale": %s }' % [
_get_local_path(),
ProjectSettings.get_setting("func_godot/default_inverse_scale_factor", 32.0) as float
]
else:
meta_properties[model_key] = '{"path": "%s", "scale": %s }' % [
_get_local_path(),

View file

@ -3,7 +3,7 @@
class_name FuncGodotFGDPointClass extends FuncGodotFGDEntityClass
## FGD PointClass entity definition.
##
## A resource used to define an FGD PointClass entity. PointClass entities can use either the [member FuncGodotFGDEntityClass.node_class]
## A resource used to define an FGD Point Class entity. PointClass entities can use either the [member FuncGodotFGDEntityClass.node_class]
## or the [member scene_file] property to tell [FuncGodotMap] what to generate on map build.
##
## @tutorial(Quake Wiki Entity Article): https://quakewiki.org/wiki/Entity
@ -17,15 +17,12 @@ class_name FuncGodotFGDPointClass extends FuncGodotFGDEntityClass
func _init() -> void:
prefix = "@PointClass"
@export_group ("Scene")
## An optional [PackedScene] file to instantiate on map build. Overrides [member FuncGodotFGDEntityClass.node_class] and [member script_class].
@export var scene_file: PackedScene
@export_group ("Scripting")
## An optional [Script] resource to attach to the node generated on map build. Ignored if [member scene_file] is specified.
@export var script_class: Script
@export_group("Build")
## Toggles whether entity will use `angles`, `mangle`, or `angle` to determine rotations on [FuncGodotMap] build, prioritizing the key value pairs in that order.
## Set to [code]false[/code] if you would like to define how the generated node is rotated yourself.
@export var apply_rotation_on_map_build : bool = true
@ -33,3 +30,91 @@ func _init() -> void:
## Toggles whether entity will use `scale` to determine the generated node or scene's scale. This is performed on the top level node.
## The property can be a [float], [Vector3], or [Vector2]. Set to [code]false[/code] if you would like to define how the generated node is scaled yourself.
@export var apply_scale_on_map_build: bool = true
## An optional [Array] of [FuncGodotFGDPointClassDisplayDescriptor] that describes how this Point Entity should appear in the map editor.
## When using multiple display descriptors, only the first element found without [member FuncGodotFGDPointClassDisplayDescriptor.conditional]
## will be used as the default display asset. If no descriptor is found without a condition, the last descriptor will become the default.[br][br]
## Conditional display descriptors will be written to the FGD in the order set in the array.[br][br]
## [color=orange]WARNING:[/color] Multiple descriptors are only supported by TrenchBroom! They will be omitted on export when
## [member FuncGodotFGDFile.target_map_editor] is not set to [enum FuncGodotFGDFile.FuncGodotTargetMapEditors.TRENCHBROOM].
@export var display_descriptors: Array[FuncGodotFGDPointClassDisplayDescriptor] = []
func _build_model_branch_text(descriptor: FuncGodotFGDPointClassDisplayDescriptor) -> String:
if not descriptor:
return ''
var model_string: String = ''
var uses_options: bool = false
if not descriptor.scale.is_empty() or not descriptor.skin.is_empty() or not descriptor.frame.is_empty():
uses_options = true
if not uses_options:
return descriptor.display_asset_path
model_string = '{ \"path\": %s' % descriptor.display_asset_path
if not descriptor.skin.is_empty():
model_string += ', \"skin\": %s' % descriptor.skin
if not descriptor.frame.is_empty():
model_string += ', \"frame\": %s' % descriptor.frame
if not descriptor.scale.is_empty():
model_string += ', \"scale\": %s' % descriptor.scale
model_string += " }"
return model_string
func _build_model_text() -> String:
var model_string: String = ''
if display_descriptors.is_empty():
return model_string
if display_descriptors.size() == 1:
return _build_model_branch_text(display_descriptors[0])
model_string = '{{'
var default_display: FuncGodotFGDPointClassDisplayDescriptor
for i in display_descriptors.size():
var d: FuncGodotFGDPointClassDisplayDescriptor = display_descriptors[i]
# Only set the first discovered descriptor without a condition to the default, which must be the last option in a list.
# If a conditional is not set, skip it.
if d.conditional.is_empty():
if not default_display:
default_display = d
else:
printerr(classname + " has a Point Class Display Descriptor without required conditionals set. Must have only 1 conditionless Display Descriptor!")
continue
model_string += '%s -> %s, ' % [d.conditional, _build_model_branch_text(d)]
if default_display:
model_string += '%s }}' % _build_model_branch_text(default_display)
else:
model_string = model_string.trim_suffix(', ')
model_string += ' }}'
return model_string
func _build_studio_text() -> String:
var display_string = ""
for d in display_descriptors:
if d.display_asset_path.find('\"') != -1:
display_string = d.display_asset_path
else:
printerr(classname + " attempting to set an invalid value to @studio format during FGD export. Only relative file paths encapsulated by quotations are valid.")
return display_string
func build_def_text(target_editor: FuncGodotFGDFile.FuncGodotTargetMapEditors = FuncGodotFGDFile.FuncGodotTargetMapEditors.TRENCHBROOM) -> String:
if not display_descriptors.is_empty():
if target_editor == FuncGodotFGDFile.FuncGodotTargetMapEditors.TRENCHBROOM:
var display_string: String = _build_model_text()
if not display_string.is_empty():
meta_properties["model"] = display_string
else:
var display_string: String = _build_studio_text()
if not display_string.is_empty():
meta_properties["studio"] = display_string
return super(target_editor)

View file

@ -0,0 +1,48 @@
@tool
@icon("res://addons/func_godot/icons/icon_godambler3d.svg")
class_name FuncGodotFGDPointClassDisplayDescriptor extends Resource
## Resource that describes how to display an FGD Point Class entity.
##
## A resource for [FuncGodotFGDPointClass] that describes how to display a point entity in a map editor.
## Values entered into the different options are taken literally: paths should be enclosed within quotation marks,
## while class property keys and integer values should omit them.[br][br]
##
## Most editors only support the [member display_asset] option. Exporting an FGD compatible with these editors will
## automatically omit the unsupported options introduced by TrenchBroom when exporting from their respective game configuration resources
## or setting [member FuncGodotFGDFile.target_map_editor] away from [enum FuncGodotFGDFile.FuncGodotTargetMapEditors.TRENCHBROOM].
##
## The extra options are considered advanced features and are unable to be evaluated by FuncGodot to ensure they were input correctly.
## Exercise caution, care, and patience when attempting to use these, especially the [member conditional] option.
##
## @tutorial(Level Design Book: Display Models for Entities): https://book.leveldesignbook.com/appendix/resources/formats/fgd#display-models-for-entities
## @tutorial(Valve Developer Wiki FGD Article: Entity Description Section): https://developer.valvesoftware.com/wiki/FGD#Entity_Description
## @tutorial(TrenchBroom Manual: Display Models for Entities): https://trenchbroom.github.io/manual/latest/#display-models-for-entities
## @tutorial(TrenchBroom Manual: Expression Language): https://trenchbroom.github.io/manual/latest/#expression_language
## Either a file path to the asset that will be displayed for this point entity, relative to the map editor's game path,
## or a class property key that can contain the path.[br][br]
## For paths, you must surround the path with quotes, e.g: [code]"models/marsfrog.glb"[/code].
## For properties, you must omit the quotes, e.g: [code]display_model_path[/code].[br][br]
## Different editors support different file types: common ones include MDL, GLB, SPR, and PNG.
@export var display_asset_path: String = ""
@export_group("TrenchBroom Options")
## Optional string that determines the scale of the display asset. This can be a number, a class property key, or
## a scale expression in accordance with TrenchBroom's Expression Language. Leave blank to use the game configuration's default scale expression.[br][br]
## [color=orange]WARNING:[/color] Only utilized by TrenchBroom!
@export var scale: String = ""
## Optional string that determines which skin the display asset should use. This can be either a number or a class property key.[br][br]
## [color=orange]WARNING:[/color] Only utilized by TrenchBroom!
@export var skin: String = ""
## Optional string that determines the appearance of a display asset based on its file type. This can be either a number or a class property key.[br][br]
## Traditional Quake MDL files will set the display to that frame of its animations (all animations in a Quake MDL are compiled into a single animation).
## GLBs meanwhile seem to set themselves to the animation assigned to an index that matches the [code]frame[/code] value.[br][br]
## [color=orange]WARNING:[/color] Only utilized by TrenchBroom!
@export var frame: String = ""
## Optional evaluation string that, when true, will force the Point Class to display the asset defined by [member display_asset_path].
## Format should be [code]property == value[/code] or some other valid expression in accordance with TrenchBroom's Expression Language.[br][br]
## [color=orange]WARNING:[/color] Only utilized by TrenchBroom!
@export var conditional: String = ""

View file

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