Goal List: Null pointer referenced (solved)

Started by Sparky, Fri 13/04/2007 05:28:00

Previous topic - Next topic

Sparky

Thank you all for your continued support. I hate to post again so soon after my last query, but I ran into a problem I'm stumped by. The basic idea behind this script is to give the player a concise list of current objectives, such as "Find the four map pieces" or "Catch a golden butterfly". There will be other script elsewhere that sets updates the GUI; the following is just sets up an array for storing the goals.

These variables are declared in the global script.
Code: ags
struct goal {
  String goal_name;
  String goal_note;
  int goal_status; // 0 = not on list, 1 = incomplete, 2 = complete
};
goal goal_list[20]; // array of current goals


These functions are also in the global script. The script compiles without errors. But when the function is called in-engine I get a "null pointer referenced" error that points to the indicated line.

Code: ags
// Goal List
function add_goal(String new_goal) { // add a goal to the list
	player.Say("Right, we're calling the add_goal function now.");
	// add the goal name to the goal list
	int n = 0;
	bool already_added = false;
	while (n < 20) { // make sure it isn't already added
		if (new_goal == goal_list[n].goal_name) { //   <------------------------ NULL POINTER ERROR here
			already_added = true;
			player.Say("The goal '%s' is in slot %d, so we don't need to add it a second time.", new_goal, n);
		}
		n ++;
	}
	// The script after this line seems to work as desired, you can probably ignore it.
	if (already_added == false) {
		n = 0;
		bool exit = false;
		while (exit == false) { // find a free slot
			if (goal_list[n].goal_name == null) { // if the slot is empty
				player.Say("We're setting putting the goal '%s' in slot %d", new_goal, n);
				exit = true; // we found a free slot, so we can stop hunting
				goal_list[n].goal_name = new_goal;
				// add appropriate dialog, etc.
				if (new_goal == "valve") {
					goal_list[n].goal_note = "Find a handle for the broken valve.";
				}
				else { // if the name isn't recognized
					player.Say("add_goal isn't finding a match for that goal.");
				} // goal specific tasks
			} // empty slot check
			n ++;
		}
	}
}

If the while loop containing the offending line is commented out, everything runs without an error. The purpose of the line is to check if a string in the array is equal to the variable new_goal. It's part of a loop that exists to prevent listing the same goal twice. Perhaps there's another way to write the line?

In searching for a solution, the function String.CompareTo came up. It seemed useful at first, but it seems it needs a constant string instead of a variable. Is there a way to use this?

monkey0506

You can pass Strings through a const string parameter. That's why Chris implemented const strings vs strings. A const string parameter can be a new-style String, a string-literal ("this is some text"), or an old-style string. A String parameter can only be a new-style String or a string-literal. A string parameter can only be a string-literal or an old-style string.

I'm not sure how String.CompareTo handles null Strings, however I believe it's a known bug that you can't have a null String on the right-hand side of an operand such as == (double equal signs). The solution would be to try something like:

Code: ags
if ((goal_list[n].goal_name != null) && (new_goal == goal_list[n].goal_name)) {


Or perhaps String.CompareTo would work, but again, I'm not sure how it handles null Strings.

Ashen

Nope, String.CompareTo has the same problem with null values. The only solution is to add the goal_list[n].goal_name != null check, or possibly to set all of them to an empty String (goal_list[n].goal_name = "";) at game start. (Obviously, you'd then need to change the goal_list[n].goal_name == null check later in the code to goal_list[n].goal_name == "".)
I know what you're thinking ... Don't think that.

SupSuper

Quote from: monkey_05_06 on Fri 13/04/2007 07:14:44
You can pass Strings through a const string parameter. That's why Chris implemented const strings vs strings. A const string parameter can be a new-style String, a string-literal ("this is some text"), or an old-style string. A String parameter can only be a new-style String or a string-literal. A string parameter can only be a string-literal or an old-style string.

I'm not sure how String.CompareTo handles null Strings, however I believe it's a known bug that you can't have a null String on the right-hand side of an operand such as == (double equal signs). The solution would be to try something like:

Code: ags
if ((goal_list[n].goal_name != null) && (new_goal == goal_list[n].goal_name)) {


Or perhaps String.CompareTo would work, but again, I'm not sure how it handles null Strings.
I think you'll have to queue up the conditions though, since that way the script will still check both conditions if it's null and throw the error. This should be error-free:
Code: ags
if (goal_list[n].goal_name != null)
if (new_goal == goal_list[n].goal_name) {
Programmer looking for work

strazer

No, the so-called "lazy evaluation" was implemented in AGS v2.71.

Sparky

I never would have guessed to try that workaround. Here's the updated script.
Code: ags
		if ((goal_list[n].goal_name != null) && (goal_list[n].goal_name == new_goal)) {
			already_added = true;
		}
		n ++;

The script is up and running now, with no errors in sight. Thanks to all.
Quote from: strazer on Sat 14/04/2007 00:42:57
No, the so-called "lazy evaluation" was implemented in AGS v2.71.
Is combining if statements using && or || considered bad practice? I don't know much about scripting, but it seems to be tidier and more readable in cases like this where there's only a single check. If, on the other hand we were doing something like
Code: ags
		if ((boolean 1 == true) {
			if (boolean 2 == true) {
				do stuff
			}
			else if (boolean 2 == false) {
				do other stuff
			}
		}
I could see using the longer form being more sensible.

strazer

Quote
if ((goal_list[n].goal_name != null) && (goal_list[n].goal_name == new_goal)) {

This wouldn't have worked prior to AGS v2.71 since it used to process both expressions, regardless of whether the first one was already false or not. So if goal_list[n].goal_name were null, the second expression would still throw up an error.
That's why you had to break such if-statements up into two parts as SupSuper said.

But as of AGS v2.71, if the first expression is false, the second one won't even be processed. So if you use a current version of AGS your use of the && operator is perfectly fine.

SMF spam blocked by CleanTalk