Jump to content

Scope of local variables


Guest lsass.exe

Recommended Posts

Guest lsass.exe

Hi there,

 

I have some questions about local variables. I understand that they are saved for each cre separately, that they can be used by that cre exclusively and that they are set/checked for that cre specifically. But I don't know the extend of the latter.

 

Let's say I have a CHAIN which starts in npc1's dialog-file “bnpc1†and then goes on to involve 3 npcs plus charname.

 

CHAIN
IF ~~ THEN bnpc1 state1
~blablabla~
== bnpc2 ~other blablabla~
== bnpc3 IF ~some tricky conditions are true~ THEN ~some very annoying blablabla~ DO ~SetGlobal("theimportantlocalvariablefornpc3","LOCALS",1)~
END
++ ~answer 1~ + state2
++ ~answer 2~ + state3

 

Is that local variable set for the npc3 cre or for the npc1 cre?

 

 

 

And to take it further:

 

CHAIN
IF ~~ THEN bnpc1 anotherstate1
~blablabla~
== bnpc2 ~other blablabla~
== bnpc3 IF ~GLOBAL("somelocalvariableofnpc3","LOCALS",1)~ THEN ~some very annoying blablabla~
END
++ ~answer 1~ + anotherstate2
++ ~answer 2~ + anotherstate3

 

Are the local variables of npc3 checked or doesn’t that work because the local variables of npc1 are checked?

 

 

I know that these might be very stupid questions (so plz have mercy) but I’d like do know before I start hacking code into my keyboard and then spending weeks with searching through all of that stuff for wrong usages of variables. :rolleyes:

Link to comment

People have an aversion to using GLOBAL variables instead of LOCAL variables, but I have no idea why.

 

If ever there was a reason to use a global, this is it.

 

This would set a variable that could be used by any creature in the game or by any script in the game.

IF
 TimeOfDay(NIGHT)
 Global("##Night","GLOBAL",0)
THEN
 RESPONSE #100
SetGlobal("##Night","GLOBAL",1)
END

You'd never have to worry about it again. If you wanted to use locals for three NPCs, you'd have to do this

IF
 TimeOfDay(NIGHT)
 Global("##Night","GLOBAL",0)
THEN
 RESPONSE #100
ActionOverride("npc1",SetGlobal("##Night","LOCALS",1)
ActionOverride("npc2",SetGlobal("##Night","LOCALS",1)
ActionOverride("npc3",SetGlobal("##Night","LOCALS",1)
SetGlobal("##Night","GLOBAL",1)
END

You'll note that you're doing four times the work, since, in the example I used above, I've still got to set the global to avoid a script loop.

 

Also, there is no way to check for one creature's local variables in another's script.

 

I pretty much limit myself to using a local variable only when I know for a fact that I will only be looking for that variable once, and only a short time later. If I'm not sure that I won't want to check for it again, or if I'm not sure when, exactly the creature will run that script block, or have that dialgue, I'll use a global.

Link to comment

LOCALS, I have found, are only really useful for AI scripting, where it is nice to have a series of buffs relying on timers to maintain, as well as CastNAttack for Mages and Clerics. Past this, it is weird to use it for banters or something else, simply because you can rely on a GLOBAL for something which another character can start, referencing one banter, and so on.

 

IE, LOCALS would be fine to use if nobody but Mazzy was being modified for banters by your mod, and she started every single one. However, if you want Mazzy to speak to Valygar, and then Valygar to mull it over and reply a day later, without using GLOBALS, you end up using lots of ActionOverride()s and other whatnot, making your code a) takes longer to write, b) harder for both you and anyone checking it/altering it later with your consent to maintain what is going on and how.

 

This applies especially for quests. Should you want to get Mazzy and Valygar to go hunt this evil Mage together, using LOCALS only, how would you get the Mage to spawn? Sure, Mazzy's script could spawn it, as could anything else if they See() Mazzy and Valygar. However, to stop this happening prematurely, Mazzy/Valygar would have to ActionOverride(SetGlobal("MazzyAndValygarKillMages","LOCALS",1)) to whatever is spawning them. Use a GLOBAL, and again, it is all cool.

 

On a side (and slightly related note), what is the scope of AREA variables? If I EXTEND_TOP_REGEXP to spawn something randomly, and want to make a relevant comment if it has spawned in an area, and even if I leave without realizing it is there, still make the comment, could I use an AREA variable to make Bartenders talk about some weird and wonderful breed of Ogre prancing through the Docks District?

 

EDIT: @OP, your CHAIN doesn't work, because you check for GLOBALs with Global(), not GLOBAL(). (I'm not sure if it will get you an error, but you might want to bear it in mind.)

 

Icen

Link to comment

== bnpc3 IF ~some tricky conditions are true~ THEN ~some very annoying blablabla~ DO ~SetGlobal("theimportantlocalvariablefornpc3","LOCALS",1)~

 

is pretty much the equivalent of an I_C_T - a set of I_C_Ts, "chained together", or CHAIN. To tell you the truth, that might be an interesting point to test - I am of the belief that the action carries over to the last speaker, just like in an I_C_T, but that the only LOCALS that are readable bu the dialog sequence are the ones stored on the original speaker. The way to test this is to try it, with something like the following, and then see which .cre has the variable set. A fun little test!

 

tag on NPC1, and

CLUAConsole:SetGlobal("npc1talks","LOCALS",1)

 

tag on NPC2, and

CLUAConsole:SetGlobal("npc2talks","LOCALS",1)

 

tag on NPC3, and

CLUAConsole:SetGlobal("npc3talks","LOCALS",1)

 

 

and then run the dialog (by CLUAConsole:SetGlobal("TEST","GLOBAL",1) )

IF ~Global("TEST","GLOBAL",1)~ THEN bnpc1 anotherstate1
~Heya, test commencing:~
== bnpc1 IF ~Global("npc1talks","LOCALS",1)~ THEN ~Hey, I read my own LOCALS! I confirm npc1talks is set!~ DO ~SetGlobal("WhosGotPotato1","LOCALS",1)~
== bnpc2 IF ~Global("npc2talks","LOCALS",1)~ THEN ~Hey, I read my own LOCALS! I confirm npc2talks is set!~  DO ~SetGlobal("WhosGotPotato2","LOCALS",1)~
== bnpc3 IF ~Global("npc3talks","LOCALS",1)~ THEN ~Hey, I read my own LOCALS! I confirm  npc3talks is set!~  DO ~SetGlobal("WhosGotPotato3","LOCALS",1)~
END
++ ~OK, setting the variables through a passthrough state, going back to test again... ~ EXTERN bnpc1 test2

CHAIN ~bnpc1~ test2
~Hey, isn't this fun - let's play "Who's Got The Potato?"~
== bnpc2 ~Sure, I'll play...~
== bnpc3 ~Sure, me too.~
END
++ ~Yep, ok, let's play.~ EXTERN bnpc1 extrastateforinsuringglobalsset


CHAIN ~bnpc1~ extrastateforinsuringglobalsset
~OK, starting now...~ EXTERN ~bnpc1~ test3

CHAIN ~bnpc1~ test3
~Heya, play is commencing now:~
== bnpc1 IF ~Global("WhosGotThePotato1","LOCALS",1)~ THEN ~I see Potato 1~ 
== bnpc1 IF ~Global("WhosGotThePotato2","LOCALS",1)~ THEN ~I see Potato 2~ 
== bnpc1 IF ~Global("WhosGotThePotato3","LOCALS",1)~ THEN ~I see Potato 3~ 
== bnpc2 IF ~Global("WhosGotThePotato1","LOCALS",1)~ THEN ~I see Potato 1~ 
== bnpc2 IF ~Global("WhosGotThePotato2","LOCALS",1)~ THEN ~I see Potato 2~ 
== bnpc2 IF ~Global("WhosGotThePotato3","LOCALS",1)~ THEN ~I see Potato 3~ 
== bnpc3 IF ~Global("WhosGotThePotato1","LOCALS",1)~ THEN ~I see Potato 1~ 
== bnpc3 IF ~Global("WhosGotThePotato2","LOCALS",1)~ THEN ~I see Potato 2~ 
== bnpc3 IF ~Global("WhosGotThePotato3","LOCALS",1)~ THEN ~I see Potato 3~ 
END
++ ~OK, done with the test. ~ DO ~SetGlobal("TEST","GLOBAL",0)~ EXIT

But fundamentally, you are probably going to want to use GLOBAL for this, just like berelinde said, rather thanm fighting through it all. I suspect only the LOCALS set on the originating .cre are active or referenceable by the dialog.

Link to comment
LOCALS, I have found, are only really useful for AI scripting, where it is nice to have a series of buffs relying on timers to maintain, as well as CastNAttack for Mages and Clerics. Past this, it is weird to use it for banters or something else, simply because you can rely on a GLOBAL for something which another character can start, referencing one banter, and so on.
They can also be useful for one-off events, like, Did I start dialogue once already when I saw PC.

 

In general, if you know you'll never need to use it again, LOCALS is a fine choice; also if you know nothing else in the game will ever care about the variable, but that gets a little tougher if you change your mind down the road.

 

Like berelinde wants to say but doesn't quite get it out, simply using GLOBAL all the time is the cheap and easy way to make sure you get it right every time.

 

On a side (and slightly related note), what is the scope of AREA variables? If I EXTEND_TOP_REGEXP to spawn something randomly, and want to make a relevant comment if it has spawned in an area, and even if I leave without realizing it is there, still make the comment, could I use an AREA variable to make Bartenders talk about some weird and wonderful breed of Ogre prancing through the Docks District?
I'm not sure what you're asking about? Generally, an AREA variable works only while you are in the specified area (the variable is saved in the ARE file; like LOCALS go in an effect on the CRE and GLOBAL variables wind up in the GAM). In reality, it can be influenced by the stupid master area and what other areas are loaded, so it'll sometimes work if you check it from another area, but it's not something you should rely on.

 

If you need something that operates independent of a specific party member and spans areas, you want a GLOBAL.

Link to comment
Guest lsass.exe

First, thanks for the answers.

 

Well, let’s see:

 

@ berelinde

I don’t have an aversion against GLOBAL variables, I just recently read about very slow savegames in megamods due to really huge amounts of GLOBALs and I just think, that instead of adding my own pile (which could be hundreds at the end of ToB, who knows), I could use LOCALS where it is possible.

 

 

@ Icendoan

Yeah, I know the limitations of LOCALS and I won’t use them for the stuff you mentioned. :rolleyes:

 

 

@ cmorgan

I think I’ll test it.

 

 

 

 

As I said, I don’t want to use LOCALS for quest tracking, general referencing, banter progress, relationship status or stuff like that, but i.e. for banter interjaction tracking.

 

Let’s take that one:

== bnpc3 IF ~InParty("npc3")~ THEN ~some very annoying blablabla~ DO ~SetGlobal("theimportantlocalvariablefornpc3","LOCALS",1)~

 

With this variable set (or not set) I would than decide which interjection npc3 would make in another banter, depending on whether he/she was present in the first one or not.

 

== bnpc3 IF ~InParty("npc3") GLOBAL("theimportantlocalvariablefornpc3","LOCALS",0)~ THEN ~ some very annoying blablabla ~
== bnpc3 IF ~ InParty("npc3") GLOBAL("theimportantlocalvariablefornpc3","LOCALS",1)~ THEN ~some completely different but still very annoying blablabla~

 

And I just think that I don’t need more GLOBALs for that kind of stuff, when there are already hundreds or even thousands of GLOBALs in the savegame. But if LOCALS are only readable for the CRE that started the dialog, I think I will fall back to the GLOBALs… :mwaha:

Link to comment

If you do test it, for god's sake see my edits - the garbage I typed out had some oddities that will mess up your test! I added an extra passthrough, because it seems to take 1.5 passthrough states to set a variable and allow the same conversation to see it. Plus typos. Lots of typos :rolleyes: .

Link to comment

Local variables aren't going to affect the load times on your saved games, since local variables are stored as well. If they weren't, you'd get the BioWare gang repeating talks and/or banters every time you turned the computer back on. Using locals instead of globals *may* reduce the time it takes an area to load by as much as a couple tenths of a second. What makes mega-saves load so slowly, and area transitions go so slowly, is the time it takes all those extended scripts to load, files to be accessed, override to be loaded into RAM, etc.

 

Long load times is getting to be more of an issue, though. That's what patch 24699 was trying to fix. It broke other things, but there you go.

 

You also need to remember that every time your NPC makes an interjection, you're going to be creating a global variable. If your NPC has 200 interjections, that's 200 globals added. Don't laugh. Some NPCs really do have that many. Haldamir probably has close to 50 (though you'll never get that many in one game), and he's pretty quiet, as far as mod NPCs go. Regardless, whether you're running on a mega or running on a bare install, you're still only going to have 5 party members plus a PC.

Link to comment

Quests usually just increment the same variable, but the interjection ones are all individual. They're the state labels, actually.

 

I_C_T FILENAME 42 statelabel

== MyNPC IF ~Conditions(usual)~ ~blah, blah~

END

 

If you compile that, then decompile FILENAME and look for state 42, you'll see that it's now got a new EXTERN MyNPC, and if you go to MyNPC.dlg and decompile that, you'll see a line

 

DO ~SetGlobal("statelabel","GLOBAL",1)~

 

There isn't anything you can do to shut this off... well, apart from not writing any interjections, that is.

Link to comment
Local variables aren't going to affect the load times on your saved games, since local variables are stored as well. If they weren't, you'd get the BioWare gang repeating talks and/or banters every time you turned the computer back on. Using locals instead of globals *may* reduce the time it takes an area to load by as much as a couple tenths of a second. What makes mega-saves load so slowly, and area transitions go so slowly, is the time it takes all those extended scripts to load, files to be accessed, override to be loaded into RAM, etc.

It's not about loading times. The game itself slows down when the number of globals exceeds a certain limit. You get constant "stuttering", when scripts are running, and it can become totally unplayable.

(I only debugged this for a friend, so I can't give you an exact number. Stuttering got worse when you added more globals. [linear])

This might be architecture dependant and it certainly depends on the processing power of your machine.

Link to comment

I'd be interested in seeing how using local variables would help. Is it really going to take less time to process a couple dozen locals than it would to process the same number of globals? They're still going to get checked evey script pass.

 

If it's that variable heavy, chances are good that it's going to be fairly script-heavy as well, and that can really slow things down.

 

The worst lag I ever had, I had Saerileth, Tsujatha, Amber, Keto, and somebody else (don't remember who), and I also had overloaded bottomless bags and gradual drow item disintegration. I couldn't even walk across Waukeen's Promenade, so it wasn't like the game had a lot of stored variables at that point. Did a rapid reassessment of my playing plans and uninstalled everything but Amber and it ran great.

 

It would be hard to test this, or would require more knowledge about scripting than I possess, but the only way to tell for sure that it's the sheer number of variables and not massive script overload would be to have a script that would generate a brand new global after a specific time interval, like every two seconds, and see how long it takes to develop lag.

Link to comment

If you use locals instead of globals, then their scope (or visibility) will be restricted to a much smaller number of objects.

This means, only the creature or area which needs to see them will be bothering with them.

You should always prefer locals to globals whenever possible.

Link to comment

I finally had some time to test this and my results are as followed: the LOCALS of the cre starting the chain are checked. Well, I guess I'll have to use GLOBALs for all the tracking of stuff I use only once in some banter later.

 

 

 

 

PS

The code I used to test.

BEGIN PWeli

IF ~Global("elinalocalstest1","LOCALS",1)~ THEN BEGIN 3
SAY ~the elinalocalstest1 is set~
IF ~~ THEN EXIT
END

CHAIN
IF WEIGHT #0 ~Global("test1","GLOBAL",1)~ THEN pweli 1
~Chain started by elina~ DO ~SetGlobal("test1","GLOBAL",2)~
= ~I set the elinalocalstest1 now.~ DO ~SetGlobal("elinalocalstest1","LOCALS",1)~
== pwwed ~I set the wedgelocalstest1 now.~ DO ~SetGlobal("wedgelocalstest1","LOCALS",1)~
EXIT

CHAIN
IF WEIGHT #1 ~Global("test1","GLOBAL",3)~ THEN pweli 2
~2nd chain started by elina~ DO ~SetGlobal("test1","GLOBAL",4)~
== pweli IF ~Global("elinalocalstest1","LOCALS",1)~ THEN ~my elinalocalstest1 is set~
== pweli IF ~Global("wedgelocalstest1","LOCALS",1)~ THEN ~i have the wedgelocalstest1 set~
== pwwed IF ~Global("wedgelocalstest1","LOCALS",1)~ THEN ~my wedgelocalstest1 is set~
== pwwed IF ~Global("elinalocalstest1","LOCALS",1)~ THEN ~i have the elinalocalstest1 set~
EXIT


BEGIN PWwed

IF ~Global("wedgelocalstest1","LOCALS",1)~ THEN BEGIN 2
SAY ~the wedgelocalstest1 is set~
IF ~~ THEN EXIT
END

CHAIN
IF WEIGHT #0 ~Global("test2","GLOBAL",3)~ THEN pwwed 1
~2nd chain started by wedge.~ DO ~SetGlobal("test2","GLOBAL",4)~
== pwwed IF ~Global("wedgelocalstest1","LOCALS",1)~ THEN ~my wedgelocalstest1 is set~
== pwwed IF ~Global("elinalocalstest1","LOCALS",1)~ THEN ~i have the elinalocalstest1 set~
== pweli IF ~Global("elinalocalstest1","LOCALS",1)~ THEN ~my elinalocalstest1 is set~
== pweli IF ~Global("wedgelocalstest1","LOCALS",1)~ THEN ~i have the wedgelocalstest1 set~
EXIT

Link to comment

Archived

This topic is now archived and is closed to further replies.

×
×
  • Create New...