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
14 changes: 13 additions & 1 deletion addons/tbloader/src/plugin.gd
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,26 @@ class_name TBPlugin
var map_control: Control = null
var editing_loader: WeakRef = weakref(null)

var import_plugin: TBMapImportPlugin = TBMapImportPlugin.new()

func _enter_tree():
set_icons(true)

map_control = create_map_control()
map_control.set_visible(false)
add_control_to_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, map_control)

add_import_plugin(import_plugin)

func _exit_tree():
set_icons(false)

remove_control_from_container(EditorPlugin.CONTAINER_SPATIAL_EDITOR_MENU, map_control)
map_control.queue_free()
map_control = null

remove_import_plugin(import_plugin)

func _handles(object):
return object is TBLoader

Expand All @@ -40,7 +46,13 @@ func create_map_control() -> Control:

func build_meshes():
var loader = editing_loader.get_ref()
loader.build_meshes()
if loader.get_scene_file_path() != null: # from imported
var interface = get_editor_interface()
var fs = interface.get_resource_filesystem()
fs.reimport_files([loader.get_scene_file_path()])
interface.reload_scene_from_path(loader.get_tree().edited_scene_root.get_scene_file_path())
else:
loader.build_meshes()

func set_icons(on):
var editor_interface = get_editor_interface()
Expand Down
19 changes: 12 additions & 7 deletions src/builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ Builder::~Builder()
{
}

Node *Builder::get_owner() const
{
return m_loader->get_owner() == nullptr ? m_loader : m_loader->get_owner();
}

void Builder::load_map(const String& path)
{
UtilityFunctions::print("Building map ", path);
Expand Down Expand Up @@ -71,7 +76,7 @@ void Builder::build_worldspawn(int idx, LMEntity& ent)
// Create node for this entity
auto container_node = memnew(Node3D());
m_loader->add_child(container_node);
container_node->set_owner(m_loader->get_owner());
container_node->set_owner(get_owner());

// Decide generated collision type
ColliderType collider = ColliderType::None;
Expand Down Expand Up @@ -164,7 +169,7 @@ void Builder::build_entity_custom(int idx, LMEntity& ent, LMEntityGeometry& geo,

auto instance = scene->instantiate();
m_loader->add_child(instance);
instance->set_owner(m_loader->get_owner());
instance->set_owner(get_owner());

if (instance->is_class("Node3D")) {
set_entity_node_common((Node3D*)instance, ent);
Expand Down Expand Up @@ -236,7 +241,7 @@ void Builder::build_entity_light(int idx, LMEntity& ent)
light->set_color(Color(color.x / 255.0f, color.y / 255.0f, color.z / 255.0f));

m_loader->add_child(light);
light->set_owner(m_loader->get_owner());
light->set_owner(get_owner());
}

void Builder::build_entity_area(int idx, LMEntity& ent)
Expand Down Expand Up @@ -266,7 +271,7 @@ void Builder::build_entity_area(int idx, LMEntity& ent)
// Create the area
auto area = memnew(Area3D());
m_loader->add_child(area);
area->set_owner(m_loader->get_owner());
area->set_owner(get_owner());
area->set_position(center);

// Create collision shape for the area
Expand Down Expand Up @@ -387,7 +392,7 @@ void Builder::add_collider_from_mesh(Node3D* node, Ref<ArrayMesh>& mesh, Collide
auto collision_shape = memnew(CollisionShape3D());
collision_shape->set_shape(mesh_shape);
node->add_child(collision_shape, true);
collision_shape->set_owner(m_loader->get_owner());
collision_shape->set_owner(get_owner());
}

void Builder::add_surface_to_mesh(Ref<ArrayMesh>& mesh, LMSurface& surf)
Expand Down Expand Up @@ -439,7 +444,7 @@ MeshInstance3D* Builder::build_entity_mesh(int idx, LMEntity& ent, Node3D* paren

// Set the layers that the mesh instance will be rendered in
mesh_instance->set_layer_mask(m_loader->get_visual_layer_mask());
mesh_instance->set_owner(m_loader->get_owner());
mesh_instance->set_owner(get_owner());
mesh_instance->set_name(instance_name);

// Create mesh
Expand Down Expand Up @@ -529,7 +534,7 @@ MeshInstance3D* Builder::build_entity_mesh(int idx, LMEntity& ent, Node3D* paren
StaticBody3D* static_body = memnew(StaticBody3D());
static_body->set_name(String(mesh_instance->get_name()) + "_col");
parent->add_child(static_body, true);
static_body->set_owner(m_loader->get_owner());
static_body->set_owner(get_owner());
add_collider_from_mesh(static_body, collision_mesh, colshape);
break;
}
Expand Down
2 changes: 2 additions & 0 deletions src/builder.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,6 @@ class Builder
String material_path(const char* name);
Ref<Texture2D> texture_from_name(const char* name);
Ref<Material> material_from_name(const char* name);

Node *get_owner() const;
};
14 changes: 11 additions & 3 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,23 @@
#include <godot_cpp/core/class_db.hpp>

#include <tb_loader.h>
#include <map_import.h>

using namespace godot;

void register_tbloader_types(ModuleInitializationLevel p_level)
{
if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) {
return;
switch (p_level)
{
case MODULE_INITIALIZATION_LEVEL_EDITOR:
ClassDB::register_class<TBMapImportPlugin>();
break;
case MODULE_INITIALIZATION_LEVEL_SCENE:
ClassDB::register_class<TBLoader>();
break;
default:
break;
}
ClassDB::register_class<TBLoader>();
}

void unregister_tbloader_types(ModuleInitializationLevel p_level)
Expand Down
166 changes: 166 additions & 0 deletions src/map_import.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@

#include <gdextension_interface.h>
#include <godot_cpp/core/error_macros.hpp>
#include <godot_cpp/core/class_db.hpp>

#include <godot_cpp/classes/editor_import_plugin.hpp>
#include <godot_cpp/classes/global_constants.hpp>
#include <godot_cpp/classes/packed_scene.hpp>
#include <godot_cpp/classes/resource_saver.hpp>

#include <godot_cpp/variant/dictionary.hpp>
#include <godot_cpp/variant/packed_string_array.hpp>
#include <godot_cpp/variant/typed_array.hpp>
#include <godot_cpp/variant/utility_functions.hpp>

#include <tb_loader.h>
#include <map_import.h>

TBMapImportPlugin::TBMapImportPlugin()
{
}

TBMapImportPlugin::~TBMapImportPlugin()
{
}

String TBMapImportPlugin::_get_save_extension() const
{
return "scn";
}

String TBMapImportPlugin::_get_importer_name() const
{
return "tbloader.map";
}

String TBMapImportPlugin::_get_resource_type() const
{
return "PackedScene";
}

String TBMapImportPlugin::_get_visible_name() const
{
return "Map";
}

bool TBMapImportPlugin::_get_option_visibility(const String &path, const StringName &option_name, const Dictionary &options) const
{
return true;
}

double TBMapImportPlugin::_get_priority() const
{
return 1.0;
}

int64_t TBMapImportPlugin::_get_import_order() const
{
return IMPORT_ORDER_SCENE - 1; // after textures, before scenes
}

template <typename N>
inline Dictionary assemble_import_option(String name, PropertyHint type, String hint, N default_value)
{
Dictionary dictionary;
dictionary["name"] = name;
dictionary["hint_string"] = hint;
dictionary["property_hint"] = type;
dictionary["default_value"] = default_value;
return dictionary;
}

template <typename N>
inline Dictionary assemble_import_option(String name, N default_value)
{
Dictionary dictionary;
dictionary["name"] = name;
dictionary["default_value"] = default_value;
return dictionary;
}

TypedArray<Dictionary> TBMapImportPlugin::_get_import_options(const String &path, int64_t preset_index) const
{
bool quake = preset_index == 1;
TypedArray<Dictionary> out;

out.append(assemble_import_option("map/inverse_scale", 16));

out.append(assemble_import_option("lighting/unwrap_uv2", false));

out.append(assemble_import_option("options/collisions_enabled", true));
out.append(assemble_import_option("options/skip_hidden_layers", true));
out.append(assemble_import_option("options/nearest_texture_filter", quake));
out.append(assemble_import_option("options/clip_texture_path", PROPERTY_HINT_ENUM_SUGGESTION, "special/clip,tools/toolsclip", quake ? "special/clip" : ""));
out.append(assemble_import_option("options/skip_texture_path", PROPERTY_HINT_ENUM_SUGGESTION, "special/skip,tools/toolsskip", quake ? "special/skip" : ""));
out.append(assemble_import_option("options/visual_layer_mask", PROPERTY_HINT_LAYERS_3D_RENDER, "", 1));

out.append(assemble_import_option("entities/common_entities", true));
out.append(assemble_import_option("entities/entities_folder_path", PROPERTY_HINT_DIR, "", "res://entities"));
out.append(assemble_import_option("entities/textures_folder_path", PROPERTY_HINT_DIR, "", "res://textures"));

return out;
}

int64_t TBMapImportPlugin::_get_preset_count() const
{
return 2;
}

String TBMapImportPlugin::_get_preset_name(int64_t preset_index) const
{
switch (preset_index)
{
case 0:
return "Default";
case 1:
return "Quake";
default:
return "Unknown";
}
}

PackedStringArray TBMapImportPlugin::_get_recognized_extensions() const
{
PackedStringArray array;
array.append("map");
return array;
}

int64_t TBMapImportPlugin::_import(const String &source_file, const String &save_path, const Dictionary &options, const TypedArray<String> &platform_variants, const TypedArray<String> &gen_files) const
{
Ref<PackedScene> scene = memnew(PackedScene);

TBLoader *loader = memnew(TBLoader);
loader->set_name(source_file.get_file());

// Map
loader->set_map(source_file);
loader->set_inverse_scale(options["map/inverse_scale"]);

// Lighting
loader->set_lighting_unwrap_uv2(options["lighting/unwrap_uv2"]);

// Options
loader->set_collision(options["options/collisions_enabled"]);
loader->set_skip_hidden_layers(options["options/skip_hidden_layers"]);
loader->set_filter_nearest(options["options/nearest_texture_filter"]);
loader->set_clip_texture_name(options["options/clip_texture_path"]);
loader->set_skip_texture_name(options["options/skip_texture_path"]);
loader->set_visual_layer_mask(options["options/visual_layer_mask"]);

// Entities
loader->set_entity_common(options["entities/common_entities"]);
loader->set_entity_path(options["entities/entities_folder_path"]);
loader->set_texture_path(options["entities/textures_folder_path"]);

loader->build_meshes();
scene->pack(loader);

auto save_path_formatted = save_path + String(".scn");
int64_t err = ResourceSaver::get_singleton()->save(scene, save_path_formatted);

ERR_FAIL_COND_V_MSG(err != OK, err, "Cannot save scene to file '" + save_path_formatted + "'.");
return err;
}

43 changes: 43 additions & 0 deletions src/map_import.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#pragma once

#include <gdextension_interface.h>

#include <godot_cpp/godot.hpp>
#include <godot_cpp/core/defs.hpp>

#include <godot_cpp/classes/editor_import_plugin.hpp>
#include <godot_cpp/variant/packed_string_array.hpp>

using namespace godot;

class TBMapImportPlugin : public EditorImportPlugin
{
GDCLASS(TBMapImportPlugin, EditorImportPlugin);

protected:
static void _bind_methods() {}

public:

TBMapImportPlugin();
~TBMapImportPlugin();

virtual String _get_save_extension() const override;
virtual String _get_importer_name() const override;
virtual String _get_resource_type() const override;
virtual String _get_visible_name() const override;

virtual bool _get_option_visibility(const String &path, const StringName &option_name, const Dictionary &options) const override;

virtual double _get_priority() const override;
virtual int64_t _get_import_order() const override;

virtual TypedArray<Dictionary> _get_import_options(const String &path, int64_t preset_index) const override;
virtual int64_t _get_preset_count() const override;
virtual String _get_preset_name(int64_t preset_index) const override;

virtual PackedStringArray _get_recognized_extensions() const override;

virtual int64_t _import(const String &source_file, const String &save_path, const Dictionary &options, const TypedArray<String> &platform_variants, const TypedArray<String> &gen_files) const override;
};