GetLocalInt and SetLocalInt issues (coupled with weird onHeartBeat issues)


#1

So from some reason, the variable iGrow always remains 1, yet according to my script it should increase by 1 every time the beetle eats. I’m puzzled as to why. This script goes on the “OnUsed” event of a mushroom placeable.

//This script makes any beetle that uses this mushroom placeable get bigger
//by destroying it and creating its bigger version (via the creature ResRef)

void main()
{
    object oBeetle = GetObjectByTag("HZ_BEETLE"); //this script only affects beetles with this tag
    object oUsed = GetLastUsedBy(); //the last creature that tried to "use" the mushroom
    int ifull = 3; //how many mushrooms does a beetle need to eat in order to grow

    if (oBeetle == oUsed) //checks if the beetle was indeed the last creature that tried to use
    {
      DelayCommand(2.0, DestroyObject(OBJECT_SELF)); //the mushroom is now being eaten, so it'll destroy itself in 2 seconds
      int iGrow = GetLocalInt(oBeetle, "Growth"); //a local int variable that exists for the beetle that determines how many mushrooms it ate
       if (iGrow< ifull) //if the beetle hasn't eaten enough mushrooms then it adds to the sGlow count
        {
        AssignCommand(oBeetle, ActionSpeakString("iGrow is "+IntToString(iGrow))); //debug check
        int iGrow = iGrow+1;
        SetLocalInt(oBeetle, "Growth", iGrow);
        AssignCommand(oBeetle, ActionSpeakString("iGrow is now "+IntToString(iGrow))); //debug check
        }
        else if (iGrow == ifull) //if the beetle has eaten enough then...
        {
        AssignCommand(oBeetle, ActionSpeakString(IntToString(iGrow))); //debug check
        iGrow = 0; //nullifies iGrow for the next round
        SetLocalInt(oBeetle, "Growth", iGrow); //stores it in the local int data
        string sBeetleResRef = GetResRef(oBeetle); //The beetles are ordered in resref based on their size, this code gets the resref string number that indicates the beetle size
        string sBeetleSize = GetSubString(sBeetleResRef, 7, 7); //get the 7th character (count starts at 0) of resref string
        int iBeetleSize = StringToInt(sBeetleSize); //turn the string that indicates the beetle size to int
        iBeetleSize = iBeetleSize + 1;  //adds +1
        string sNewBeetle  = "btlstag" + IntToString(iBeetleSize); //adds the +1 number to the resref name chosen for the beetles
        location oLocation = GetLocation(oBeetle); //gets the current beetle location before the transformation
        object oNewBeetle = CreateObject(OBJECT_TYPE_CREATURE, sNewBeetle, oLocation); //create the new beetle at the same location the old beetle was
        DelayCommand(1.0, AssignCommand(oBeetle, DestroyObject(oBeetle))); //make the old beetle disappear

        }
    }

}

Note that the OnSpawn of the beetle declares the local int “Growth” as 0

I was trying to test separately by using the following script on the OnHeartBeat of a chicken NPC, but the chicken doesn’t speak at all! So I’m completely baffled now

void main()
    {
        SetLocalInt(OBJECT_SELF, "testvar", 1);
        int mynumber = GetLocalInt(OBJECT_SELF, "testvar");
        ActionSpeakString("Speak it! " + IntToString(mynumber));
        mynumber = mynumber+1;
        SetLocalInt(OBJECT_SELF, "testvar", mynumber);
        ActionSpeakString("Speak it! " + IntToString(mynumber));
        mynumber = mynumber+1;
        SetLocalInt(OBJECT_SELF, "testvar", mynumber);
    }

#2

I think your problem is in this section of code -

        int iGrow = GetLocalInt(oBeetle, "Growth"); //a local int variable that exists for the beetle that determines how many mushrooms it ate

        if (iGrow < ifull) //if the beetle hasn't eaten enough mushrooms then it adds to the sGlow count
        {
            AssignCommand(oBeetle, ActionSpeakString("iGrow is " + IntToString(iGrow))); //debug check
            
            int iGrow = iGrow + 1;
            
            SetLocalInt(oBeetle, "Growth", iGrow);
            AssignCommand(oBeetle, ActionSpeakString("iGrow is now " + IntToString(iGrow))); //debug check
        }

You are declaring the variable iGrow twice. This isn’t illegal but the one declared in the body of the if() statement is a separate and different iGrow to the one declared first. It only exists within the { }.

Just try changing

            int iGrow = iGrow + 1;

to

            iGrow++;

and see if that fixes it.

TR


#3

PROTIP: use [code]...[/code] blocks for syntax highlighting.

Instead of

if(oBeetle == oUsed) { ... }

use

if(GetTag(oUsed) == "HZ_BEETLE") { ... }

EDIT:

Try SpeakString instead of ActionSpeakString.


#4

EDIT: Hopefully, I have interpreted what you were after here …

I believe this is what you need …

//int iGrow = iGrow+1;  // You already defined iGrow a few lines earlier.
        SetLocalInt(oBeetle, "Growth", iGrow+1);

Lance.


#5

TR’s answer is better because now the speakstring needs the same thing. You might as well do the math only once. Although it could easily be left as iGrow = iGrow + 1 rather than using the post increment operator.


#6

Sounds good to me. :slight_smile:


#7

You guys are awesome! Solved both my issues :slight_smile: Although I spoke to a programmer about it and she was surprised the compiler didn’t give me an error for declaring igrow twice (though I overlooked it, I would imagine a better compiler/language/whatever would alert you to this issue at least)

Well regardless, my beetles are all happy now as they can grow in a moderate pace :slight_smile: (and a chicken can sing!)

This is for my ecosystem project, which I’ll post about in youtube sooner or later (I actually already did post in youtube once but only the basics that I’ve accomplished)


#8

Your programmer is mistaken, at least for C like languages. It’s not an error. Some better compilers will (optionally) give a warning in this case. There is nothing illegal about what you did there. The second declaration was in the limited scope of those {}s after the if. It was shadowing the variable of the higher scope. If they had been both in the same scope it would have been an error. Because of confusion like you had it’s not generally a good practice.


#9

…which is why it is a good practice to declare all variables you are going to use in a function right at the beginning. And even resists the temptation to assign their values right away (maybe except some defaults).


#10

I disagree with that one. Just don’t name them the same :slight_smile: The system does work to set aside space for variables on the stack (admittedly not that much especially if not assigned values). If you only need it inside an if statement protected code block then declaring the variable outside that is wasted work every time the branch is not taken. The NWNscript compilers don’t do much (any) of the optimizations real compilers do. I do agree that if you are declaring them all up front you don’t need to initialize them until they are needed, especially for cases where there are function calls involved in the initialization and you may end up not needing that value at all.


#11

Besides, I’m not always sure how many/which variables I’ll need, so declaring them ahead of time is a lot of pressure!! :exploding_head::exploding_head:

It’s amazing the amount of errors I get just by not paying attention to the silly stuff like spaces and misspells (just happened to me 5 minutes ago with include " x0_i0_position "… I copy pasted it from the NWNLexicon, but turns out the NWNLexicon has spaces on the quotations which produce an error. )

Well, I bet modern compilers let you know that stuff (i.e. your variable Blahblahblah is very similiar to the variable Blahblahbla…etc)…but I love NWN, so I don’t really care. :grin:


#12

@meaglyn Of course an unused variable is wasted time and space (which is why -Wunused exists), but such small overhead is often a good price for not shooting yourself in the foot. You can always re-purpose variables anyway (and writing to such “global” ints and floats doesn’t change the data segment boundary, so you save some cycles), but then they need generic names and you have to keep track which does what and where.

They do …while the toolset’s nwscript compiler can’t even tell you have unbalanced parenthesis (and the built-in editor doesn’t highlight them).


#13

For most purposes, when it comes to best practice, legible robust code trumps performance. Better to spend less time on code that works out of the box than save a few cycles if no one ever notices the difference.

Of course, when performance is mission-critical, rules are made to be broken…


#14

I totally agree with that and said nothing that supported another argument. Variables declared closer to where they are used is generally easier to read, I find.

This particular case was a mistake that declaring all variables up front would not have effected anyway since the second declaration was not needed at all.

@shacker I did not mean that they are unused completely, just unused in the code paths that get executed for a given set of inputs. -Wunused would not effect that.

Keeping track of which does what and where supports my claim that it’s better to declare them closer to where they are used :slight_smile:

multiplayer scripting can very easily push the limits of what the single thread can do so I do try to keep unneeded work to a minimum out of habit. I don’t believe that is in conflict with readable code that works out of the box.


#15

That said, declaration upfront is widely regarded as best practice, precisely because it avoids the scope issues that easily befuddle - all inline declarations then being visible errors.

I have to admit to being lazy about that sometimes, in hobby scripts, but I’d always recommend it to a newcomer.


#16

EDIT: looks like @Proleric ninja’d me.

Which is why I suggest beginners should probably start with declaring everything at start. To have an unobstructed view on what they’re working with.

Does anyone know the approximate value of NWN’s call stack size limit? It often throws too_many_instructions too soon for my taste.


#17

These are unrelated. Val did have it declared up above (not right at the top but still in a higher scope. I don’t see that having that first declaration at the top scope would have prevented the second declaration mistake. Also, unobstructed view could include the declaration of the variable you need in your while loop and rather than having to scroll back up to the top to see it it could be right there. But anyway … to each his/her own :slight_smile:

The stack is not related to the too many instructions. Then entire scripting engine (the machine on which scripts execute) is a stack based VM. No registers etc. TMI is just a fixed counter that’s there to prevent bad scripts from hanging the game. I don’t know off hand if there is an hard limit of the size of the stack used for the VM implementation.


#18

There are a couple of other limits. I think you can go about 32 function calls deep before it just fails silently. Real pain when that happens.

Similarly you can only go about 7 ExecutScripts() deep before the same thing happens. It just goes right by as if the statement was not there. That’s a fun one to debug too especially the first time when you don’t know about it.


#19

c#

Error CS0136: A local variable named ‘levels’ cannot be declared in this scope because it would give a different meaning to ‘levels’, which is already used in a ‘parent or current’ scope to denote something else
Build failed.

 
and iirc, FXCop (a code-policy analyzer for c#), suggests that vars be declared as tightly as possible

/shrug


#20

Fair enough. There are probably other languages for which it is an actual error as well. NWscript, however, is based on C not C#. I never touch microsoft-isms myself if I can help it :slight_smile: