as you can tell from the title, I am facing a very strange parser error. One that I´ve tried to understand many times but have failed to do so.
Here is the error:
Now, I know this means that the program couldn´t find the BASE_NICE_2_ACHIEVEMENT variable. But the problem is that it´s stored inside an autoload (The autoloads at the time of the error are shown below). Quite humorously the next one to load is ResourceCopies, which is exactly where BASE_NICE_2_ACHIEVEMENT is located. So far so good. The strange thing though is that the script where the error occurs only exists in one place, the Game autoload. The Game autoload, as you can see, isn´t loaded either though and should be loaded after ResourceCopies. So it would´t make any sense as to why a script that isn´t even loaded is trying to load something else.
This isn´t even the strange part though. The variable that the program tries to grab but fails is a type Roll_Filter_Achievement_Base_Class(Custom type, obviously). And inside that class I am declaring a variable of type Die(Also custom type). I´m just declaring it, not assigning it a value. And the curious thing is, if I don´t declare this type, the error doesn´t occur.
And this is where it stops making any sense to me. After all why would adding a type hint change the loading order of autoloads. Maybe it doesn´t, maybe it always loads like that and the type hint just makes the script invalid or something, and so the variable as well. I don´t know, the engine doesn´t tell me anything else.
Now, before you ask. Yes the Die class is used successfully in other parts of my program. It doesn´t cause any parser errors there. It´s just in this one specific case. And what bugs me the most, is that it´s not even an instanced Die, it´s just a type hint. How can that cause an error like this?
“Could not resolve external class member” means there is some parse error in that class member’s script, not that it’s not found. Godot needs to know/be able to work with the variable type at compile time (before actually assigning a value) which explains why the error is thrown even when the node isn’t instantiated.
But I can’t explain why the error only appears in that script… maybe reloading the project will fix it?
I deal with this all the time - sometimes compiler is stuck in a recursive loop when it finds a class that it can’t parse properly, e.g. due to syntax error. Then this will cause other classes to also fail the parsing, and it might propagate to other classes etc. Then even if you fix the original syntax error that started this chain, the compiler is lost and will still show you some parsing errors.
Simply reloading the project will most likely resolve this issue completely, if there are no actual issues with the code anymore.
Or going into the classes script (ctrl + click on the class type), saving that script and returning to the other class where the parse error appeared - usually works too, it’s faster than reloading the project, but is a bit less intuitive.
Here are the two scripts you asked for. Thanks for helping btw.
Die.gd
extends Resource
class_name Die
signal Highest_Roll_Possible_Rolled
signal Rolls_Per_Throw_Updated
const MAXIMUM_ROLLS_PER_THROW : int = 6
@export var Number_Of_Sides : int = 20
@export var Rolls_Per_Throw : int = 1:
set(Value):
Rolls_Per_Throw = Value
Rolls_Per_Throw_Updated.emit()
@export var Total_Roll_Amount : int = 0
@export var Total_Roll_Sum : float = 0
@export var Previous_Rolls : Array[Die_Roll] = []
@export var All_Previous_Rolls : Array[Array] = []
@export var All_Rolls_This_Session : Array[Array]
@export var Total_Roll_Amount_This_Session : int = 0
@export var Total_Roll_Sum_This_Session : float = 0
@export var Settings : Die_Settings = Die_Settings.new()
@export var Should_Show_Only_Roll_From_This_Session_In_Roll_Log : bool = false
@export var Highest_Roll_Streak : int = 0
@export var Lowest_Roll_Streak : int = 0
func Get_Roll_Average() -> float:
if Total_Roll_Amount == 0:
return 0.0
return Total_Roll_Sum / Total_Roll_Amount
func Get_Roll_Average_This_Session() -> float:
if Total_Roll_Amount_This_Session == 0:
return 0.0
return Total_Roll_Sum_This_Session / Total_Roll_Amount_This_Session
func Change_Amount_Of_Sides(New_Amount : int) -> void:
Number_Of_Sides = New_Amount
Total_Roll_Amount = 0
Total_Roll_Sum = 0
Previous_Rolls = []
All_Previous_Rolls = []
Reset_Session_Values()
Game.Master_SF.Achievements.Dimensional_Paradox.Try_To_Unlock(New_Amount)
Game.Master_SF.Achievements.Nice.Try_To_Unlock(New_Amount)
func Roll(Cosmetic : bool = false) -> Die_Roll:
var New_Roll : Die_Roll = Die_Roll.new()
var Rolled_Amount : int = randi_range(1, Number_Of_Sides)
New_Roll.Value = Rolled_Amount
if not Cosmetic:
Total_Roll_Amount += 1
Total_Roll_Sum += Rolled_Amount
Total_Roll_Amount_This_Session += 1
Total_Roll_Sum_This_Session += Rolled_Amount
Game.Master_SF.Total_Times_Rolled += 1
Game.Master_SF.Achievements.First_Steps.Try_To_Unlock(Game.Master_SF.Total_Times_Rolled)
Game.Master_SF.Achievements.Roll_Trainee.Try_To_Unlock(Game.Master_SF.Total_Times_Rolled)
Game.Master_SF.Achievements.Roll_Pro.Try_To_Unlock(Game.Master_SF.Total_Times_Rolled)
Game.Master_SF.Achievements.Roll_God.Try_To_Unlock(Game.Master_SF.Total_Times_Rolled)
Game.Master_SF.Achievements.Touch_Grass.Try_To_Unlock(Game.Master_SF.Total_Times_Rolled)
if Rolled_Amount == Number_Of_Sides:
Highest_Roll_Possible_Rolled.emit()
Highest_Roll_Streak += 1
else:
Highest_Roll_Streak = 0
if Rolled_Amount == 1:
Lowest_Roll_Streak += 1
else:
Lowest_Roll_Streak = 0
New_Roll.Highest_Roll_In_A_Row = Highest_Roll_Streak
New_Roll.Lowest_Roll_In_A_Row = Lowest_Roll_Streak
Game.Master_SF.Achievements.Nice_2.Try_To_Unlock(New_Roll, Number_Of_Sides)
Game.Master_SF.Achievements.Primal.Try_To_Unlock(New_Roll, Number_Of_Sides)
Game.Master_SF.Achievements.Primal_Prime.Try_To_Unlock(New_Roll, Number_Of_Sides)
Game.Master_SF.Achievements.Lucky_Bastard.Try_To_Unlock(New_Roll, Number_Of_Sides)
Game.Master_SF.Achievements.Poor_You.Try_To_Unlock(New_Roll, Number_Of_Sides)
return New_Roll
func Get_Rolls(Cosmetic : bool = false) -> Array[Die_Roll]:
var Rolls : Array[Die_Roll]
for i in Rolls_Per_Throw:
Rolls.append(Roll(Cosmetic))
if not Cosmetic:
Previous_Rolls = Rolls
All_Previous_Rolls.append(Rolls)
All_Rolls_This_Session.append(Rolls)
return Rolls
func Reset_Session_Values() -> void:
All_Rolls_This_Session = []
Total_Roll_Amount_This_Session = 0
Total_Roll_Sum_This_Session = 0
This is not the inhereted script, but instead the parent class where I inserted the Die variable seen in my original post. (Here the test : Die variable has been removed)
Roll_Filter_Achievement_Base_Class.gd
extends Achievement
class_name Roll_Filter_Achievement_Base_Class
@export var AMOUNT_OF_CONSECUTIVE_ROLLS_NEEDED : int = 0
@export var REQUIRED_AMOUNT_OF_SIDES : int = 1
@export var Consecutive_Rolls : Array[Die_Roll] = []
func Try_To_Unlock(Roll : Die_Roll, Amount_Of_Sides : int) -> void:
if Unlocked or Amount_Of_Sides < REQUIRED_AMOUNT_OF_SIDES:
return
if Filter(Roll, Amount_Of_Sides):
Consecutive_Rolls.append(Roll)
else:
Consecutive_Rolls.clear()
if Consecutive_Rolls.size() >= AMOUNT_OF_CONSECUTIVE_ROLLS_NEEDED:
Unlock()
Unlock_Attempt_Was_Made.emit()
func Filter(Roll : Die_Roll, Amount_Of_Sides :int) -> bool:
if Roll and Amount_Of_Sides:
return true
return false
func Get_Unlocking_Progess() -> float:
if Unlocked:
return 1.0
return float(Consecutive_Rolls.size()) / float(AMOUNT_OF_CONSECUTIVE_ROLLS_NEEDED)
Sorry, but it’s pretty hard to find the rootcause without access to the full codebase.
My best guess is that adding the var test: Die line, you’re creating a cyclic dependency between your classes (e.g. Die → Die_Roll → … → Roll_Filter_Achievement_Base_Class → Die), which the Engine can’t resolve and throws this error. You might need to restructure your code to avoid this cyclic dependency.
Don´t worry. It´s very nice of you to have spent that much time trying to help me. But yeah that would have been my guess too, since I´ve have a similiar issue before and there that was the solution. But here I can´t find any. I just checked the Die_Roll class, just in case, and it´s just int´s so no luck there unfortunately.
Just FYI - I also tried reproducing this error on my end with some minimal classes, but I couldn’t get it at all. I deliberately added some obvious cyclic dependencies, but it all worked fine without any errors.
I’m out of ideas