Plugins and custom paths

Godot Version

4.3 +

Question

When one installs a plugin from the library, the dialogue lets one choose where the plugin will go. This means res://addons is not guaranteed to be the path.

Now, if you are writing a plugin and you need to load("res://addons/myfoo/something") to get a resource ready, you can’t know where the plugin is, afaict.

I was using preload('something') which does accept relative paths, but am running into troubles with exporting the game. (long story).

Is there any way to obtain the directory of where a plugin is installed?

1 Like

Maybe something like this in the plugin script?

self.get_script().resource_path 
# Returns the absolute path to the script file

or

self.get_script().resource_path.get_base_dir() 
# Returns the path of the directory the script is in
1 Like

Good plan. Here’s the wrinkle :slight_smile: :

class_name Globs extends Object

# This const was great but...
# const PREF_AVOID_SCENE = preload("src/dabs/pref_avoid.tscn")
# ... it forces user to include the pref_avoid.tscn in the final build.
# So I needed a way to step around that issue, hence the getter
static var PREF_AVOID_SCENE:
	get:
		if not Engine.is_editor_hint():
			return null
		# TODO This absolute path will break the plugin should the user
		# install to some other directory
		return load("res://addons/dabber/src/dabs/pref_avoid.tscn")

I use it in other files as:

var s = Globs.PREF_AVOID_SCENE # etc.

So, Globs is like a namespace to that file and PREF_AVOID_SCENE has to be static (to avoid an instance).

The wrinkle: It won’t let me access get_script() from within a static getter (or func)!

1 Like

I found this other thread, maybe it can help: Is there a way to instance a class based on a string or enum in GDScript? - #4 by system

There’s absolutely gotta be a simpler way to do this but I was thinking maybe you could use ProjectSettings.get_global_class_list() which returns an Array[Dictionary] and the dictionaries include the name and path of each global class.

So I thought you could cycle through all those dictionaries until you find one where if dictionary[“class”] == “Globs” then get dictionary[“path”].get_base_dir() to get the plugin directory. Or something like that maybe?

1 Like

That’s a great idea too! Thanks. Will report back.

1 Like

ETA: Added another version that works whether the plugin is off or on. See End (Thanks to @fiumals for the idea.)

The solution I went with was suggested to me on Mastodon by Rie (Rie 🔜 GodotCon :godot: (@tracefree@mastodon.gamedev.place) - Gamedev Mastodon). It uses ProjectSettings.get_setting("editor_plugins/enabled") which returns an array of paths where the plugin.cfg files live.

Here’s the code:

static var DEFAULT_SPHERE:
	get:
		if not Engine.is_editor_hint():
			return null
		var path := _get_plugin_dir()
		if not path: return null
		# And here I can use the path to find a resource relative to it!
		return load("%s/assets/default_mesh.tres" % path)


static func _get_plugin_dir() -> String:
	# ProjectSettings.get_setting returns a PackedStringArray hence cast   ↓
	var all:Array = ProjectSettings.get_setting("editor_plugins/enabled") as Array
	# if missing, well, shizz?
	if not all: return ""
	var seek = all.filter(func(s): return "dabber" in s)
	# if not in there, the plugin is turned off.
	if not seek: return ""
	# e.g. "res://addons/dabber/plugin.cfg"
	var pth : String = seek[0]
	pth = pth.get_base_dir()

	return pth

If the plugin is disabled, one may still want to obtain the path, so try this:

class_name dabGlobals extends Object

static var DEFAULT_SPHERE:
	get:
		if not Engine.is_editor_hint(): return null
		var p = _get_class_dir("dabGlobals")
		# p → res://addons/dabber
		if not p: return null
		return load("%s/assets/default_mesh.tres" % p)


# This one works whether the plugin is on or off!
static func _get_class_dir(classname:StringName)->String:
	var a = ProjectSettings.get_global_class_list()
	var find = a.filter(func(d): return classname == d["class"])
	var ret : String = ""
	if not find.is_empty():
		ret = find[0]["path"].get_base_dir()
	return ret
1 Like