Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ bin/
*.dblite
*.obj
*.os
*.import
.DS_Store
.vscode
24 changes: 13 additions & 11 deletions addons/tbloader/src/plugin.gd
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
extends EditorPlugin
class_name TBPlugin

var map_control: Control = null
var map_control: Control = HBoxContainer.new()
var editing_loader: WeakRef = weakref(null)

func _enter_tree():
set_icons(true)

map_control = create_map_control()
add_button_to_control("Build Meshes", "build_meshes")
add_button_to_control("Build FGD", "build_fgd")
map_control.set_visible(false)
add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, map_control)

Expand All @@ -28,20 +29,21 @@ func _make_visible(visible: bool):
func _edit(object):
editing_loader = weakref(object)

func create_map_control() -> Control:
var button_build_meshes = Button.new()
button_build_meshes.flat = true
button_build_meshes.text = "Build Meshes"
button_build_meshes.connect("pressed", Callable(self, "build_meshes"))

var ret = HBoxContainer.new()
ret.add_child(button_build_meshes)
return ret
func add_button_to_control(text, callback):
var button = Button.new()
button.flat = true
button.text = text
button.connect("pressed", Callable(self, callback))
map_control.add_child(button)

func build_meshes():
var loader = editing_loader.get_ref()
loader.build_meshes()

func build_fgd():
var loader = editing_loader.get_ref()
loader.build_fgd()

func set_icons(on):
var editor_interface = get_editor_interface()
var base_control = editor_interface.get_base_control()
Expand Down
2 changes: 1 addition & 1 deletion godot-cpp
116 changes: 116 additions & 0 deletions src/fgd_gen.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#include <fgd_gen.h>

#include <godot_cpp/classes/dir_access.hpp>
#include <godot_cpp/classes/resource_loader.hpp>
#include <godot_cpp/classes/packed_scene.hpp>
#include <godot_cpp/classes/project_settings.hpp>
#include <godot_cpp/classes/script.hpp>
#include <godot_cpp/variant/variant.hpp>

#include <tb_loader.h>

FGDGen::FGDGen(TBLoader* loader)
{
m_loader = loader;
this->fgd_properties = std::map<String, std::function<String(String)>>();
this->fgd_properties["fgd_size"] = [](String size) {
return vformat("size(-%s -%s -%s, %s %s %s)", size, size, size, size, size, size);
};
}

FGDGen::~FGDGen()
{
}

PackedStringArray FGDGen::find_all_entity_paths_in_dir(String dir_path) {
Ref<DirAccess> dir = DirAccess::open(dir_path);
if (dir->get_open_error() != Error::OK) {
UtilityFunctions::print("Error opening directory");
return PackedStringArray();
}

PackedStringArray entity_paths = PackedStringArray();
for (auto entity_path : dir->get_files()) {
if (entity_path.ends_with(".tscn")) entity_paths.append(entity_path);
}
std::transform(entity_paths.begin(), entity_paths.end(), entity_paths.begin(), [&dir_path, this](String path) {
// return (dir_path + "/" + path).replace(this->m_loader->get_entity_path() + "/", "");
return (dir_path + "/" + path);
});

PackedStringArray subdirs = dir->get_directories();
if (subdirs.size() > 0) {
for (int i = 0; i < subdirs.size(); i++) {
String subdir_path = dir_path + "/" + subdirs[i];
PackedStringArray sub_entity_paths = this->find_all_entity_paths_in_dir(subdir_path);
entity_paths.append_array(sub_entity_paths);
}
}

return entity_paths;
}

String FGDGen::generate_fgd_for_entity(String entity_path) {
auto resource_loader = ResourceLoader::get_singleton();
Ref<PackedScene> scene = resource_loader->load(entity_path);
Node* instance = scene->instantiate();
// auto properties = instance->get_property_list();
Ref<Script> attached_script = instance->get_script();

auto stringify_variant_type = [&] (int64_t t) {
switch (t) {
case Variant::Type::BOOL: return "bool";
case Variant::Type::INT: return "integer";
case Variant::Type::FLOAT: return "float";
case Variant::Type::COLOR: return "color";
case Variant::Type::DICTIONARY: return "flags";
case Variant::Type::ARRAY: return "choices";
default: return "string";
}
};

String type = "PointClass";
String name = entity_path.replace(this->m_loader->get_entity_path() + "/", "").replace("/", "_").replace(".tscn", "");
String description = instance->get_name();
String fgd_properties = "";
String custom_properties = "";

if (attached_script.is_valid()) {
TypedArray<Dictionary> property_list = attached_script->get_script_property_list();
for (int i = 0; i < property_list.size(); i++) {
Dictionary property = property_list[i];
if (property["hint_string"].stringify().find(".gd") != -1 || (int64_t)property["type"] == Variant::Type::NIL) continue;
auto property_name = property["name"].stringify();
if (property_name == "fgd_solid") {
if (instance->get(property_name)) type = "SolidClass";
continue;
}
if (this->fgd_properties.find(property_name) != this->fgd_properties.end()) {
fgd_properties += this->fgd_properties[property_name](instance->get(property_name).stringify()) + " ";
continue;
}
else {
custom_properties += vformat("\n\t%s(%s) : \"%s\" : %s : \"%s\"", property["name"], stringify_variant_type(property["type"]), property["name"], instance->get(property_name), "");
}
}
// fgd_properties += "\n";
custom_properties += "\n";
}

String fgd_entry = vformat("@%s %s = %s : \"%s\" [ %s ]", type, fgd_properties, name, description, custom_properties);

return fgd_entry;
}

void FGDGen::generate() {
PackedStringArray entity_paths = this->find_all_entity_paths_in_dir(m_loader->get_entity_path());

auto file_path = String(ProjectSettings::get_singleton()->get_setting("application/config/name")) + ".fgd";
Ref<FileAccess> f = FileAccess::open(file_path, FileAccess::WRITE);
f->store_string("// Generated by TBLoader\n");
for (auto entity_path : entity_paths) {
String fgd_line = this->generate_fgd_for_entity(entity_path);
f->store_string(fgd_line + "\n");
}
UtilityFunctions::print("Generated FGD file at " + file_path);
}
26 changes: 26 additions & 0 deletions src/fgd_gen.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#pragma once

#include <godot_cpp/godot.hpp>
#include <godot_cpp/variant/utility_functions.hpp>

#include <godot_cpp/classes/file_access.hpp>

#include <map>

using namespace godot;

class TBLoader;

class FGDGen
{
public:
TBLoader* m_loader;
public:
FGDGen(TBLoader* loader);
~FGDGen();
void generate();
private:
PackedStringArray find_all_entity_paths_in_dir(String dir_path);
String generate_fgd_for_entity(String entity_path);
std::map<String, std::function<String(String)>> fgd_properties;
};
7 changes: 7 additions & 0 deletions src/tb_loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <godot_cpp/variant/utility_functions.hpp>

#include <builder.h>
#include <fgd_gen.h>

void TBLoader::_bind_methods()
{
Expand Down Expand Up @@ -37,6 +38,7 @@ void TBLoader::_bind_methods()

ClassDB::bind_method(D_METHOD("clear"), &TBLoader::clear);
ClassDB::bind_method(D_METHOD("build_meshes"), &TBLoader::build_meshes);
ClassDB::bind_method(D_METHOD("build_fgd"), &TBLoader::build_fgd);

ADD_GROUP("Map", "map_");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "map_resource", PROPERTY_HINT_FILE, "*.map"), "set_map", "get_map");
Expand Down Expand Up @@ -209,3 +211,8 @@ void TBLoader::build_meshes()
builder.load_map(m_map_path);
builder.build_map();
}

void TBLoader::build_fgd() {
FGDGen fgd_gen(this);
fgd_gen.generate();
}
1 change: 1 addition & 0 deletions src/tb_loader.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,5 @@ class TBLoader : public Node3D

void clear();
void build_meshes();
void build_fgd();
};