Parse Error: Class "name" hides a global script class.

Godot Version

v4.7.1.stable.official [a13da4feb]

Question

I am creating a mod for a game (Pathogenic) by extending one of the existing classes. I used the included Mod Tool plugin to create the extension file. I tried various solutions from other existing posts, but none of them worked:

  • I renamed the extension script to be different from the super script
  • I gave it a distinct class_name as opposed to just using extends “res://…"
  • I re-attached the script to the .tscn file (which would not be a viable solution for creating a mod anyway)

When manually ignoring the error (by resuming execution with F12 in the IDE), the program works as intended and my mod is loaded correctly.

My extension script:

class_name PlayerAmoebaExtension
extends "res://scn/player/player_amoeba/player_amoeba.gd"

## Flag indicating that the user is currently selecting the desired connections.
## Used by _unhandled_input
var _selection_active = false
## Dictionary containing possible slots as keys and and Array of their original 
## scale and color as values. Set in _highlight_connections and used reset key
## slots if placement is cancelled
var _scale_color_store: Dictionary = {} # Slot -> Array[scale,color]
## The color for highlighted but not selected slots
var _highlight_color: Color = Color(0.6, 0.6, 0, 1)
## The color for highlighted and selected slots
var _selected_color: Color = Color(0.075, 0.654, 0.149, 1.0)
## Defines by how much the highlighted slots will be enlarged
var _highlight_scale_factor: Vector2 = Vector2(0.375, 0.375)
## Holds the position at which the new slot was placed
var _new_slot_position: Vector2

func is_placing() -> bool:
	return _placement_active or _selection_active

func _commit_placement() -> void:
	composer.hide_preview()
	_hide_preview_overlay()
	_hide_slot_previews()
	_placement_active = false
	_selection_active = false
	_revert_highlights()
	should_listen_to_events = true
	release_attack_actions()
	
	var mirror_pos := _mirror_of(_new_slot_position)
	var positions := [_new_slot_position]
	if mirror_pos != _new_slot_position:
		positions.append(mirror_pos)
	await add_blobs(positions, _placement_radius, _placement_type)
	blob_placement_finished.emit(true)
	
	_pop_editor_mouse_filter()

func _preview_candidates_within(slot_local: Vector2, inv_anchor: Transform2D, radius: float, max_conn: int) -> Array[Slot]:
	var radius_sq: float = radius * radius
	var result: Array[Slot] = []
	for s_node in get_physical_slots():
		var s := s_node as Slot
		if not is_instance_valid(s):
			continue
		if not _placement_slot_internal and not s.internal:
			continue
		#side effect without which the game crashes!
		AmoebaSlotEvolution.gather_connection_segments(self, inv_anchor)
		var existing_local: Vector2 = AmoebaSlotEvolution.slot_composer_local(s, inv_anchor)
		var d_sq := existing_local.distance_squared_to(slot_local)
		if d_sq > radius_sq:
			continue
		result.append(s)
	return result

func _unhandled_input(event: InputEvent) -> void:
	if _placement_active:
		if event.is_action_pressed(&"accept"):
			_new_slot_position = _placement_cursor_local()
			_check_placement()
			get_viewport().set_input_as_handled()
		elif event.is_action_pressed(&"ui_cancel") or (event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_RIGHT):
			if _placement_cancellable:
				# Draft pick: emitting placed=false makes apply() return false, so
				# add_mutation refunds the pick and the editor reopens the draft.
				_cancel_placement()
			# World pickup: aborting would silently waste the already-consumed reward,
			# so just swallow ESC / right-click — the blob must be placed.
			get_viewport().set_input_as_handled()
	elif _selection_active:
		if event.is_action_pressed(&"accept"):
			_select_connection()
			get_viewport().set_input_as_handled()
		elif event.is_action_pressed(&"ui_cancel") or (event is InputEventMouseButton and event.pressed and event.button_index == MOUSE_BUTTON_RIGHT):
			_placement_active = true
			_selection_active = false
			_revert_highlights()
			get_viewport().set_input_as_handled()

func _check_placement():
	var key = _preview_connections_by_blob.keys()[0]
	if _preview_connections_by_blob[key].size() <= _PREVIEW_MAX_CONNECTIONS:
		pending_slot_connections = _preview_connections_by_blob.duplicate()
		_commit_placement()
	else:
		_placement_active = false
		_selection_active = true
		_highlight_connections()
	return

## Changes size and color of all slots that may be connected to the newly placed
## slot. Previous colors and scale is saved in _candidates
func _highlight_connections() -> void:
	pending_slot_connections = {}
	for slot_array: Array in _preview_connections_by_blob.values():
		for slot: Slot in slot_array:
			for child: Node in slot.visual.get_children():
				if child is Sprite2D:
					var sprite := child as Sprite2D
					if !_scale_color_store.has(slot):
						_scale_color_store[slot] = [sprite.scale, sprite.modulate]
					sprite.scale = _highlight_scale_factor
					sprite.modulate = _highlight_color
		# we only want one side to allow unambiguous cross-axis selection
		break

## Restores original size and color for all candidates highlighted by _highlight_connections
func _revert_highlights() -> void:
	for slot: Slot in _scale_color_store.keys():
		for child in slot.visual.get_children():
				if child is Sprite2D:
					var sprite := child as Sprite2D
					sprite.scale = _scale_color_store[slot][0]
					sprite.modulate = _scale_color_store[slot][1]
	_scale_color_store.clear()

## Marks already highlighted candidates
func _select_connection():
	var mouse_pos = get_global_mouse_position()
	var slot_hit = false
	for slot_array: Array in _preview_connections_by_blob.values():
		for slot: Slot in slot_array:
			var local_mouse := slot.to_local(mouse_pos)
			var hit_radius = 0
			for child in slot.visual.get_children():
				if child is Sprite2D:
					var sprite := child as Sprite2D
					if !sprite.is_visible_in_tree():
						continue
					hit_radius = sprite.texture.get_size().x * sprite.global_scale.x * 0.5
					if local_mouse.distance_to(Vector2(0,0)) < hit_radius:
						slot_hit = true
						
						if not pending_slot_connections.has(_new_slot_position):
							pending_slot_connections[_new_slot_position] = []
						
						if !pending_slot_connections[_new_slot_position].has(slot):
							pending_slot_connections[_new_slot_position].append(slot)
							sprite.modulate = _selected_color
						else:
							pending_slot_connections[_new_slot_position].erase(slot)
							sprite.modulate = _highlight_color
						
						break
				if slot_hit:
					break
			if slot_hit:
					break
		if slot_hit:
					break
	if pending_slot_connections[_new_slot_position].size() == _PREVIEW_MAX_CONNECTIONS:
		_commit_placement()

My mod script:

extends Node

# This mod changes how the slots of the amoeba connect to each other.
# The user may now chose any 3 existing slots that are within range when placing a new slot.
# External slots still cannot be connected to each other.
# Cross-axis connections are possible.
# This mod extends player_amoeba.gd and changes how inputs in the editor are handled.

const MOD_DIR := "unknown_archetype-amoeba_slot_rework" # Name of the directory that this file is in
const LOG_NAME := "unknown_archetype-amoeba_slot_rework:Main" # Full ID of the mod (AuthorName-ModName)

var mod_dir_path := ""
var extensions_dir_path := ""
var translations_dir_path := ""

func _ready() -> void:
	ModLoaderLog.info("Init", LOG_NAME)
	mod_dir_path = ModLoaderMod.get_unpacked_dir().path_join(MOD_DIR)

	# Add extensions
	var script_path = "res://mods-unpacked/unknown_archetype-amoeba_slot_rework/extensions/scn/player/player_amoeba/player_amoeba_extension.gd"
	ModLoaderMod.install_script_extension(script_path)
	ModLoaderLog.info("Done", LOG_NAME)

My console output at the point of the error:

Godot Engine v4.7.1.stable.official.a13da4feb - https://godotengine.org
OpenGL API 3.3.0 NVIDIA 560.94 - Compatibility - Using Device: NVIDIA - NVIDIA GeForce GTX 1060 6GB

INFO ModLoader:Store: Applying options override with feature tag "editor".
INFO ModLoader:Store: Applying options override with feature tag "steam".
INFO ModLoader: game_install_directory: res://
INFO ModLoader:Path: The directory for mods at path "res://mods" does not exist.
INFO ModLoader:ThirdParty:Steam: Checking workshop items, with path: "workshop/content/3808690"
INFO ModLoader:ThirdParty:Steam: The directory for mods at path "workshop/content/3808690" does not exist.
SUCCESS ModLoader: res://mods-unpacked/unknown_archetype-amoeba_slot_rework loaded.
SUCCESS ModLoader: DONE: Loaded 1 mod files into the virtual filesystem
SUCCESS ModLoader: DONE: Loaded all mod configs
INFO ModLoader: mod_load_order -> 1) unknown_archetype-amoeba_slot_rework
INFO ModLoader: Initializing -> unknown_archetype-amoeba_slot_rework
SUCCESS ModLoader: DONE: Completely finished loading mods
SUCCESS ModLoader: DONE: Installed all script extensions
SUCCESS ModLoader: DONE: Applied all scene extensions
<Game specific logs>
INFO unknown_archetype-amoeba_slot_rework:Main: Init

After resuming the execution manually, the console gives slightly more information:

E 0:04:22:593   script_extension.gd:126 @ apply_extension(): Parse Error: Class "PlayerAmoeba" hides a global script class.
  <GDScript Source>gdscript://-9223371045589737769.gd:1 @ GDScript::reload()
  <Stack Trace> script_extension.gd:126 @ apply_extension()
                mod.gd:52 @ install_script_extension()
                mod_main.gd:22 @ _ready()

I am new to Godot, so any help would be greatly appreciated!

Did your class_name used to be “name”, but has now been changed to “PlayerAmoebaExtension”, without getting rid of the error message?

When you say you reattached the script to the scene, do you use this exact script?
If you create a class you don’t want to use the script you define the class in as a script, but just create an empty script and enter extends PlayerAmoebaExtension

If you can’t get it to work using mod tool, just create it as you normally would using the engine. Create an empty script in the file system, and copy paste what you have shared here into it. Then extend from that when you want to use it in scenes. It is very easy and if it doesn’t work, you’ll be able to get better help. Most people here will not know exactly how this plugin works. I’ve never heard of it before.

No, I just used “name” in the title because the actual name does not matter. There have been other posts with the same error message but different name and I wanted to keep it general. I tried both class_name PlayerAmoebaExtension extends "resand completely omitting it by just using extends… without any class name. The error remained exactly the same, always referring to PlayerAmoebahiding some script.

I reattached the script I am extending, specifically the script PlayerAmoeba.gdin the path given in extend "res://…".
I did not try to create a class, I just used the script to extend the existing class PlayerAmoeba.As mentioned above, the current name (PlayerAmoebaExtension) was just an attempt at fixing the error and can be omitted without change.

Just creating the script elsewhere does not help me, as I am specifically trying to create a mod. I can’t just ship the whole game again just for my changes to be applied. The game came prebuild with the Mod Tool to support modding. As an aside: Other mods exist and work. They do not seem to do things any differently when it comes to extending existing scripts. Therefore, I am not sure if my issue is related to the Mod Tool at all. But then again, I am new to Godot.
If you’re just pointing at the way I created the script itself: I tried both creating it through the IDE manually and through the plugin. No change.

The name doesn’t matter but it makes it a bit confusing to try to figure out what is the problem when things are changed around like that.

When saying that you should save the script as a class and then just extend from it, i don’t mean that you should reship the game, just that you should save the script separately. If you try to create a class on a builtin script you will get an error message that reads ““class_name” isn’t allowed in built-in scripts”. It could be that you use an older version of the engine or that the plugin has not been updated or god knows. I belive that in the past it was possible to create a class_name in builtin scripts. It could be that it doesn’t show up as an error message for you because other errors are caught and cover this error, as sometimes happen when there are several errors present simultaneously.

If you just add a script anywhere in the file system (bottom left) and make it look like what you have posted here, then you should be able to extend from it in builtin scripts. You should even get offered autocompletion when entering what to extend.

Or could you already have saved it somewhere by accident? Try to ctrl+shift+f and enter PlayerAmoebaExtension to find any mention of it in the project. Could be that it pops up somewhere you don’t expect it to.

I would say that the problem is not with the code in that specific class itself, but most likely a more general problem, which is highlighted by the chosen title. But that seems like a matter of personal taste.

I really do not understand you then. The game contains a class PlayerAmoeba. I extended it in a separate script. I tried both extending it through a “raw” script, as well as a named subclass using class_name. Neither worked. I do not understand what you mean by

save the script as a class and then just extend from it

To my understanding I tried that and it failed? If you mean the super class PlayerAmoebait is already a class, so I don’t understand what you would want me to do either.

I never got any error for any built-in scripts. When resuming execution, no error appears. So I do not understand how the presented error would hide any other errors, if those do not subsequently show? Also, the class I am trying to extend is not a native class, but was created by the developers of the game. I had to look up the meaning of built-in in the context of Godot, but given that the game runs perfectly fine without issues, using named classes for built-in scripts does not seem to be the issue.

My engine is at 4.7.1 stable version (see top of post). The Mod Tool came shipped with the game.

I can add scripts anywhere, but they will not be loaded. I need something to call my code in Godot as well, no? Just creating my script somewhere is not useful. I would have to also change the call structure to refer to my code. That does not seem reasonable, given that these changes would also have to be separated from the existing code, and that I am creating a purely optional mod and my already existing changes do not work already. I do not see how creating a script somewhere in my filesystem would help with the issue at hand? My code works, I tested it (by changing super class directly. I reverted all changes and restored the original state, which is why I did not even mention it).

Using the global search feature, I get exactly one hit (see screenshot).

I already went through ~10 posts with the exact same error message and applied their suggestions to no avail. This also includes refreshing cashes, deleting indexes and reloading the project.

Can you show me the original player_amoeba.gd’s first ~10 lines and the Mod Tool’s script_extension.gd around line 126?

Also, can you first have your class extend PlayerAmoebaand see if you get the same issue?

so:

class_name PlayerAmoebaExtension
extends PlayerAmoeba 

Just read what I write. If you save the script with class_name somewhere in the file system, it will show up as an option to extend from when you create new scripts. You create the class by saving the script as a single script (not attached to a node or in a scene). You don’t need to rework this or that or reship the game or whatever else you would want to suggest as possible problems. I have written it now twice and you keep bringing up possible problems causes by using basic features of the engine.

I wish you good luck with this issue and hope that you manage to solve it.

PlayerAmoeba.gd, line 1-15:

class_name PlayerAmoeba extends Player

# Preloaded here, not as a Slot const: slot.gd preloading its own scene was a
# script<->scene cycle that fails the export binary loader (Sentry GODOT-1YE).
const slot_scene = preload("res://scn/player/bodyparts/slot.tscn")

signal blob_placement_finished(placed: bool)

@onready var composer: BlobComposer = $BlobComposer

var blobs: Array[BlobData] = []
var _baked_image_tex: ImageTexture
var _baked_normal_tex: ImageTexture
var _baked_canvas_texture: CanvasTexture
var _regen_inflight := false

script_entension.gd, line 96-135: (Indentations did not quite work or at least look weird in this comment editor, let me know if it is an issue. I added the comment to mark line 126)

static func apply_extension(extension_path: String) → Script:
# Check path to file exists
if not FileAccess.file_exists(extension_path):
ModLoaderLog.error(“The child script path ‘%s’ does not exist” % [extension_path], LOG_NAME)
return null

var child_script: Script = load(extension_path)
# Adding metadata that contains the extension script path
# We cannot get that path in any other way
# Passing the child_script as is would return the base script path
# Passing the .duplicate() would return a '' path
child_script.set_meta("extension_script_path", extension_path)

# Force Godot to compile the script now.
# We need to do this here to ensure that the inheritance chain is
# properly set up, and multiple mods can chain-extend the same
# class multiple times.
# This is also needed to make Godot instantiate the extended class
# when creating singletons.
child_script.reload()

var parent_script: Script = child_script.get_base_script()
var parent_script_path: String = parent_script.resource_path

# We want to save scripts for resetting later
# All the scripts are saved in order already
if not ModLoaderStore.saved_scripts.has(parent_script_path):
	ModLoaderStore.saved_scripts[parent_script_path] = []
	# The first entry in the saved script array that has the path
	# used as a key will be the duplicate of the not modified script
    ModLoaderStore.saved_scripts[parent_script_path].append(parent_script.duplicate()) #<- LINE 126

ModLoaderStore.saved_scripts[parent_script_path].append(child_script)

ModLoaderLog.info(
	"Installing script extension: %s <- %s" % [parent_script_path, extension_path], LOG_NAME
)
child_script.take_over_path(parent_script_path)

return child_script

I applied your suggestion, but no change.

Hmm, just before reload(), add this and report back:

print("extension_path: ", extension_path)

print("child_script: ", child_script)

print("base_script: ", child_script.get_base_script())

Either I completely misunderstand you, or your suggestion is just not helpful. The script player_amoeba.gdalready existed and my extension player_amoeba_extension.gdalso exists in the project’s file system. I am able to use autocomplete and did so during development.

Creating my script in some random location would not help with anything. The existing classes need to call my new functions and in order to do so, I would have to change them. I would then also have to publish those changes as well, given that I am creating a purely optional mod.

Thank you for your time.

extension_path: res://mods-unpacked/unknown_archetype-amoeba_slot_rework/extensions/scn/player/player_amoeba/player_amoeba_extension.gd

child_script: (res://mods-unpacked/unknown_archetype-amoeba_slot_rework/extensions/scn/player/player_amoeba/player_amoeba_extension.gd):<GDScript#-9223371046546039078>

base_script: (res://scn/player/player_amoeba/player_amoeba.gd):<GDScript#-9223371686110295754>

and if you comment out child_script.reload(), does the error go away? (it’s not the solution, just helping us to isolate it)

When “hides a global script class” appears and there is not an obvious name that hides another name, it’s typically a sign of circular dependencies. Can you make a minimal example project?

No, the error remains.

I am not sure I can, as I am new to Godot and just trying to create a mod. I am rather unfamiliar with the game’s code base myself.

If you think the issue might not be related to Godot in general, I would have to contact either the game’s or mod tool’s developers.
However, when looking up the error message online, other posts suggested that the issue previously came up somewhere in the IDE’s indexes or paths, so I thought I’d give this forum a shot first.

I don’t know if this was already suggested above but the first thing to try would be deleting the .godot folder and restarting the project.

It wasn’t suggested in this post, but in other posts I found. It did not help.

One possible solution is perhaps to just copy paste all the code from PlayerAmoeba into a new script, instead of using extends. Since the issue is shadowing this should get rid of the problem. Its not ideal but if you just want it to work without digging through everything in search of some weird bug in the plugin or engine, i believe the easiest option would be to copy paste in the entire script instead of extending from it.

You should probably do that.