Jump to content
DoctaSax

Tutorial: NVSE4+ part 1: syntax and expressions

Recommended Posts

This is the first installment of a set of guides that’ll explain and advertize some of the fancy things that come with NVSE4 and are new to the FNV modding community: UDFs, string vars and array vars. Oblivion modders unfamiliar with the more advanced functionalities of OBSE (16+) may also get some use out of it. These guides are aimed at people who understand an average vanilla script – give or take looking up some functions – but feel a little lost looking at the OBSE documents when they try to figure out how to use all that on a practical level.

 

Let’s start with the basics: some brand new syntax that does more with less. Have a look at this list of OBSE expressions.

If scripting is a language, let’s call the functions the vocab and idioms, and what you’re seeing there the syntax, the stuff you need to put it all together and make sense of it. Some of those should already be familiar to you: == != > < >= <=     + - * /       ()    && ||

Comparison, some basic math, parentheses overriding regular precedence, the logical operators AND and OR – you got that, right? If not, put this aside, come back when you do.

 

Let’s skip to the most useful of the new syntax features, by which I mean the ones we'll definitely need to get the other parts to work, as well as some that are just too handy to ignore:

1. LET

2. IF EVAL

3. "MATH" AND ASSIGN

4. WHILE LOOPS

5. SCRIPT COMPILER OVERRIDE

 

1. LET

 

In nvse4, “let” takes over from good old “set”. Sure, you can still use “set” for whatever you used it before, but “let” can do more, and it’s good practice to switch to using that whenever you work with the more advanced syntax below, and string and array vars, which we’ll discuss in another installment. Basically, all you need to remember is that instead of

Set someVar to someValue
you go

let someVar := someValue
See that := thingie there? Don't forget it. It means "assign", ie "let A := B" means "let A be B" - in a biblical "let there be light" kinda way.

 

 

2. IF EVAL

 

What goes for set-let goes for if-if eval and the elseif counterparts. “If eval” will be needed to have the compiler understand some of the more advanced stuff. Use it exclusively with anything involving array and string variables, and it won’t let you down.

If eval SomeCondition
Elseif eval SomeCondition
Else
endif
Note that you can mix old ifs with new if evals in the same if-block, if you want.

if eval someCondition
elseif someCondition
endif
 

3. "MATH" AND ASSIGN

 

+=   add and assign

-=    subtract and assign

*=   multiply and assign

/=    divide and assign

 

Indispensible, if you ask me, for quickly manipulating any float; it pretty much performs the math function on the var/value to left with the var/value to the right and immediately sets the var/value on the left to the result.  It may not look like much, but goddamn, we can finally be done with typing out lines like

Set someQuestID.someVar to ThatSameQuestID.ThatSameVAR + 1
and just switch to

let someQuestID.someVar += 1
Less typing, shorter scripts, no reason to stick to the old way. Oh, and how about those timers:

Set fTimer to fTimer + GetSecondsPassed
If fTimer > 10
               ; do something
Endif
can just be

If eval (fTimer += GetSecondsPassed) >  10
               ; do something
Endif
Use it, love it, never look back.

 

 

4. WHILE LOOPS

 

While loops pretty much combine the looping aspect of the Label/GoTo loops we’ve had so far, with the possibility of making the loop dependent on an inherent “if eval” condition (including that ‘math and assign’ stuff above).

A while loop is anything between a "while" condition and a "loop" command; it’s a block like an if-endif block:

While somecondition
    ; check/do stuff
Loop
If the while-condition is false, what’s in that block will be skipped. If the while-condition is true, the block between it and the loop command will execute, and on hitting “loop”, the script will return to the while-condition to evaluate it again. Let's get some examples in:

 

Want some chunk of code to run 80 times? Used to be you had to do:

Label 1
If iNum < 80
               Set iNum to iNum + 1
               ; run your code
               GoTo 1
Endif
The while equivalent is:

While (iNum += 1) < 81
; run your code
loop
Again, it's shorter, and it has the added advantage that nvse doesn't have to bother to keep track of label indexes, and neither do you for that matter. Additionally, the loop is represented as a block, whereas the GoTo is hardly ever on the same level of indentation as your label, and the endif to your if was rarely ever in play. That may sound a little finnicky, but the more complicated your script, the more readability is a factor. Label/GoTo is ugly.

Do note however that iNum is already added to on the first time the while-condition evaluates, so it starts at "1" rather than at "0" in the if-condition of the old example, so for an exact 80 loops, I raised the value to 81.

 

Let’s apply this to your classic formlist walk. With label/goto, your average backwards loop, doing stuff with each element in the list & then erasing it from your formlist, would look like:

Set iCount to ListGetCount rList
Label 1
If 0 < iCount
               Set iCount to iCount – 1
               Set rForm to ListGetNthForm rList iCount
               ; do stuff with rForm
               ListRemoveNthForm rList iCount
               GoTo 1
Endif
With while:

Let iCount := listgetcount rList
While 0 <= (iCount -= 1) ; note: iCount is already subtracted from rightaway, so I made it a <=
               Let rForm := ListGetNthForm rList iCount
               ; do stuff with rForm
               ListRemoveNthForm rList iCount
Loop
And of course, just erasing without doing stuff with the list's elements can be as simple as

while (listgetcount rList)
     ListRemoveNthForm rList 0
loop
 

While loops are obviously pretty sweet and a lot more elegant than Label/GoTo, but like any other loop function you should take care that it does have a chance to end and doesn't go into an infinite loop that's bound to crash your game.

while (iNum += 1) > 0
    ; do stuff
loop
is BAD: instant CTD.

Also, stop and consider what you're doing before you run a very large, intensive chunk of code in a while loop that runs a lot of times. The while loop is still supposed to run within the frame that the script it's in runs, after all. Definitely limit your while loop body to things that actually depend on the while condition being different.

 

You can stop a while loop with the "break" command, which will move the script to the line below "loop" - this is obviously something you should do whenever you can to avoid code being run that shouldn't or doesn't have to be:

let iNum := listgetcount ActorsInCellList
while (iNum -= 1) >= 0
   let rActor := ListGetNthForm ActorsInCellList iNum
   if rActor.IsChild
     let iChildAlert := 1
     break
   endif
loop
if iChildAlert == 0
   ; do stuff you don't want kids to be around
endif
You can skip a part of the while loop's body with the "continue" command, which, like a 'return', will immediately move the script back to the while-condition:

let iNum := listgetcount ActorsInCellList
while (iNum -= 1) >= 0
   let rActor := ListGetNthForm ActorsInCellList iNum
   if rActor.GetIsReference playerref
      continue
   endif
   ; do stuff you won't want to apply to the player
loop
As always, it's good practice to have those "continues" as high up in your loop's body as you can, same as a return in a spell or object script.

 

 

 

5. SCRIPT COMPILER OVERRIDE

 

Note: you may need to know a bit more about string vars, array vars and user-defined functions to really understand some of what’s below. But the compiler override definitely falls under syntax and isn’t limited to any of those subjects, so there.

 

Sure, let, if eval, while, math & assign etc. are pretty sweet in themselves already, and we haven't hit the spot of looking at combinations of UDFs, string vars, array vars and nx vars yet. Still, when we get to that, we're gonna be hampered sometimes by the vanilla script compiler itself, which doesn't accept those as parameters to vanilla and old nvse functions.

 

Example #1: let's say we have a number that we want to get the floor of, the nearest whole number lower than the float. The vanilla compiler will know what we want to do if we specify it as a float variable that we pass to the floor function as a parameter:

let iSomeInt := floor fSomeFloat
It will not understand what we want though if we tell it to get the float from an array element or from a UDF call:

let iSomeInt := floor somearray[someIndex]
let iSomeInt := floor call someUDF
will not compile. The vanilla compiler can only accept float variables as parameters to the floor function, and has no idea what's waiting in that array or that UDF at compile time anyway. Could be anything, and nuh-uh, it ain't standing for it.

To get around that, you have to switch over the script block to use the NVSE compiler instead, which you do by prepending the block name with an underscore:

Begin _Gamemode (_Menumode, _Scripteffectstart, _OnActivate, _Function, etc etc)
Let someInt := floor somearray[somekey]
Let someInt := floor (call someFunction)
End
and then you can go about your business, getting that floor, you badass. You’re basically telling the compiler to take it on faith that it’s gonna be fine, you know that there’s a float waiting under that key in that array, or that a float is bound to come back from that UDF. You’re taking over some of the responsibility from the compiler, in exchange for more freedom.  In turn, you’ll need to keep your head, not fuck up, test stuff in-game instead of relying on error reports in the geck, and use parentheses a little more to make sure the compiler doesn’t neatly compile something that can’t possibly work in-game.

 

 

Example #2: let’s say we want to check a bunch of different SPECIAL actor values on an actor and give them a bump if they’re under 5, then usually that’d mean:

ref rActor

Begin OnActivate

if rActor.GetAV Charisma < 5
   rActor.SetAV Charisma 5
endif
if rActor.GetAV Intelligence < 5
  rActor.SetAV Intelligence 5
endif
...yawn... I'm not gonna bother with the other ones. Huge waste of script space, bound to give you RSI
End
 

Now, you should know that the parameters Charisma, Intelligence etc are each in fact a string, a combination of characters. To shorten the chore of checking and setting 7 actor values, we can stick them in an array, retrieve them in a foreach loop and stick them in a string var, and just check them like that:

 

ref rActor
array_var entry
array_var somearray
string_var somestringvar

Begin _OnActivate

let somearray := ar_list "Agility", "Charisma", "Endurance", "Intelligence", "Luck", "Perception", "Strength"
foreach entry <- somearray
   let somestringvar := entry[value]
   if eval 5 > rActor.GetAV somestringvar
       rActor.SetAV somestringvar 5
  endif
loop

End
 

but, well, that's just a roundabout way of showing the override will allow a string_var rather than the actual string for the actor value. Strictly speaking we can shorten that even more:

ref rActor
array_var somearray
array_var entry

Begin _OnActivate

let somearray := ar_list "Agility", "Charisma", "Endurance", "Intelligence", "Luck", "Perception", "Strength"
foreach entry <- somearray
   if eval 5 > rActor.GetAV entry[value]
       rActor.SetAV entry[value] 5
  endif
loop

End
 

Example # 3 : just the example given in the OBSE docs, which if you understood the previous ones, you should be able to get now:

string_var axis
float pos
array_var somearray

Begin GameMode

let axis := somearray[index]  ; the axis paramater for the setpos function is held in an array as a string that can be "x", "y" or "z"
let pos := (call someFunction) + someRef.GetPos z  ; the setpos function will only accept a float for the position parameter so that needs to be calculated first
if eval axis == "z"
    setpos z pos
elseif eval axis == "y"
   setpos y pos
elseif eval axis == "x"
   setpos x pos
endif

End
becomes the epically short

begin _gamemode
  setPos someArray[index], (call SomeFunction)  + someRef.getPos z
end
 

----------------

 

An added advantage of using the script compiler override is that instead of

Set fHealth to rActor.GetAV Health  ; hey, hey or: let fHealth := rActor.GetAV somestringvar/somearrayelement, remember?
you can refer to the actor value by its code number, which you can find at the bottom of the woefully outdated nvse (read: fose) docs:

let fHealth := rActor.GetAV 16
Why is this an advantage? Well, you can do stuff with numbers that you can’t with strings like “health”. Like sticking them in an int:

Let fHealth := rActor.GetAV iSomeInt
So that you could pretty much loop through a bunch of actor values with a while and math and assign loop too, for instance:

int iSomeInt

Begin _ScriptEffectStart

Let iSomeInt := 24
While (iSomeInt += 1) < 32 ; loops through 25-31, the AV codes for your body parts’ condition
    if eval 100 > GetAV iSomeInt
         SetAV iSomeInt 100
    endif
loop

End
in case you want to make some "Doc's Super Duper Doctor's Bag" or "Instant Heal Button" something ;)

Share this post


Link to post

Looks good,

 

I'm going to have to poke around some with this array stuff, I'm sure using anything with a name like SCRIPT COMPILER OVERRIDE I can break things with a lot less effort :)

Share this post


Link to post

Well, a few comments:

 

I know what you mean but these two statements aren't equivalent.

Set someQuestID.someVar to ThatSameQuestID.ThatSameGodDamnVARThatIShouldveSpelledShorter + 1

and

let someQuestID.someVar += 1

Surely you meant

Set someQuestID.someVar to ThatSameQuestID.somevar + 1

for the first example. I'd add that another important lesson is NOT to choose over-long variable names or ones that are easy to typo.

If eval (fTimer += GetSecondsPassed) >  10
               ; do something
Endif

I've always understood that the eval version has more of a performance hit. Not an issue unless you are doing it a lot. Perhaps point this out?

 

Perhaps warn people about while loops that are over-long (or worse) never actually terminate.

Set iCount to ListGetCount rList
Label 1
If 0 < iCount
               Set iCount to iCount – 1
               Set rForm to ListGetNthForm List iCount
               ; do stuff with rForm
               ListRemoveNthForm List iCount
               GoTo 1
Endif

AGH! I loathe labels with a passion and they are never required! I can think of several ways to process each element that don't require the fearsome unconditioned jump! Mentioning them in the section that covers the more elegant while loop seems a bit strange.

Share this post


Link to post

Well, a few comments:

 

I know what you mean but these two statements aren't equivalent.

Lol, well, just an expression of frustration there ;)

 

 

If eval (fTimer += GetSecondsPassed) >  10
               ; do something
Endif
I've always understood that the eval version has more of a performance hit. Not an issue unless you are doing it a lot. Perhaps point this out?

 

I don't know if it is? I think in your average situation - I know, what is average? - that particular example shouldn't give any trouble.

 

Perhaps warn people about while loops that are over-long (or worse) never actually terminate.

Good point!

 

AGH! I loathe labels with a passion and they are never required! I can think of several ways to process each element that don't require the fearsome unconditioned jump! Mentioning them in the section that covers the more elegant while loop seems a bit strange.

Label/GoTo is all we've had in the way of looping stuff for fallout scripting until a month or 2-3 ago, so I think it makes sense that I use it for contrast when advertizing while. It's something we've quite often used so far in sexout scripting, and you're right, with while and foreach in the picture, label/goto is hardly ever needed anymore, and good riddance.

Share this post


Link to post

I don't know anything about them, or what to use them for :blush:

 

but hey, if you guys can explain that to me, I'll put it in

Share this post


Link to post

I'd be careful with the bitwise stuff. The versions of NVSE prior to the latest one that got the OBSE expressions referred to logical operations as 'bitwise', at least on the geck wiki. Make sure you're clear that you're talking about real bitwise operations and not that misnamed garbage.

 

That said, bitwise operations are easy.

 

So say you track if the player has ever killed anyone in a list of factions, e.g. if they'd ever killed an ncr, legion, pg, etc. The old school way is to make a quest with a var for each faction, init them all to zero, and then update the var to 1 if/when they kill someone in that faction. With a bitmask, you only need one var. Each bit in the value is used as an independent variable that can only hold 2 values; 0 or 1.

 

You need to decide which bit position's represent which factions. It's best if you store these values in a comment, or better yet, in vars or globals of their own so you can reference them by name. You'll setup the bitmasks with the bitwise shift-left operation. The three above might be represented with:

 

let bmHasKilledLegion = 1 << 0
let bmHasKilledNCR = 1 << 1
let bmHasKilledPG = 1 << 2
The '<<' operator is "shift left". It means take the value on the left (1, in each of these examples), and move it N positions to the left. So the PG one takes the value 1, and moves it two positions left; from 1, to 10, to 100 (binary).

 

That then sets the three bitmasks to the values 1, 2, and 4. In binary those three numbers are 001, 010, and 100. Now suppose the variable you're storing all this in is called 'hasKilledFlags' and starts off 0. If they kill an NCR, you'll set that variable to itself bitwise-or'd with bmHasKilledNCR like:

 

let hasKilledFlags = hasKilledFlags | bmHasKilledNCR
Now hasKilledFlags is equal to bmHasKilledNCR. Now some time passes and they kill a PG. You do the same thing as above, but with the PG flag.

let hasKilledFlags = hasKilledFlags | bmHasKilledPG
hasKilledFlags is now equal to 6, 110 in binary, because it's the original value 010b, bitwise anded with the PG bimask of 100b. 100 | 010 == 110.

 

When you want to check if a flag is on, you use a bitwise AND instead and compare the result to 0. If it's 0, the bit is off, anything else and it's on. So if you want to check if they killed a PG you do this:

 

let hasKilledPG = hasKilledFlags & bmHasKilledPG
This sets hasKilledPG to only the bits that are '1' in both sides of the AND. Since bmHasKilledPG (the bitmask) only has one bit on, hasKilledPG will be 0 if that bit is not on in hasKilledFlags, and nonzero otherwise. This is why they're called bitmasks. They are a mask of bits you 'overlay' onto other values with a bitwise operator. The operator defines how they are combined.

 

There are a lot of different types, but we only have OR and AND. No NAND, NOR, XOR, NOT, etc. :)

 

Off topic geeky fun fact :

 

 

 

A NAND (not-and) is an operation on two bits that returns a 1 if the two values are not the same.

 

0 nand 0 == 0

0 nand 1 == 1

1 nand 0 == 1

1 nand 1 == 0

 

In electronics, logic gates are physical implementations of logical operators; a NAND gate has two inputs and one output, and the 1 and 0 are represented by high-voltage or low-voltage. Every other bitwise operator can be "built" with a combination of NAND comparisons where the output from one is the input into another, and so, every type of logic gate can be made from NAND gates.

 

Because of this property, all computer memory (including flash), as well as the actual cpu core, is made almost entirely of complicated groupings of NAND gates.

 

NOR gates can also be used, but they require more transistors and other components, so they aren't.

 

 

 

Share this post


Link to post

Thanks, pride, I'll come to grips with it tomorrow - right now I'm incredibly stupid from putting the UDF one together. Even for something relatively simple like that, there's a lot to take into account if you want to break it all down and gradually build it back up for the benefit of... well, guys like me. :)

 

So... what's the difference with the 'falsely named' logical stuff? If I'm supposed to make it clear, I might as well expose the difference.

Share this post


Link to post

Thanks, pride, I'll come to grips with it tomorrow - right now I'm incredibly stupid from putting the UDF one together. Even for something relatively simple like that, there's a lot to take into account if you want to break it all down and gradually build it back up for the benefit of... well, guys like me. :)

 

So... what's the difference with the 'falsely named' logical stuff? If I'm supposed to make it clear, I might as well expose the difference.

The older ones are boolean/logical operators, '&&' and '||'. I'm sure you use them in comparisons already and are familiar with them. They are the "if (foo == 1) && (bar == 2)" bits.

 

It's important to not mix them up with a typo because a single or double & and | have very different meanings.

 

I cannot remember right now where I saw them referred to as bitwise operators.. I thought it was on the geck wiki, but the page that discusses them has the right names.

Share this post


Link to post

Ah actually I had that backwards.

 

In NVSE there already were bitwise functions: LogicalAnd, LogicalOr, RightShift, etc. These are the same as the shorthand in the new version.

 

In the NVSE documentation (and in their names) they are called Logical, but they are actually Bitwise.

Share this post


Link to post

Ah actually I had that backwards.

 

In NVSE there already were bitwise functions: LogicalAnd, LogicalOr, RightShift, etc. These are the same as the shorthand in the new version.

 

In the NVSE documentation (and in their names) they are called Logical, but they are actually Bitwise.

OBSE has the smae, though they are properly documented under the bitwise section. I don't understand why they named those functions logical xxx.

Share this post


Link to post

Spruced up the while loop bit; added the logical operators && and || to the list of things people should already know about before attempting the new stuff.

Share this post


Link to post

Good additions! Explaining why you do something or how to choose one of several equivalent techniques ought to be very useful to beginner.

Share this post


Link to post
let bmHasKilledLegion = 1 << 0
let bmHasKilledNCR = 1 << 1
let bmHasKilledPG = 1 << 2

Is this correct syntax, btw? Shouldn't it be "let int := int << int" or something? Which'd correspond to "set int to Leftshift int int".

 

There are a lot of different types, but we only have OR and AND. No NAND, NOR, XOR, NOT, etc. :)

Well, even the old nvse docs mention a "LogicalXOr" which I suppose is similarly really a bitwise xor. And LogicalNot ("!"), which however does seem to function more as "logical" operator (like && and ||) when placed in front of a condition that returns a boolean, even if it is a bitwise one. What a naming mess, heh.

 

I gotta wonder though whether bitwise operations really qualify as new NVSE4 stuff, considering it's been in there since the fose days and the only new thing about it I can see is some of the symbols. I'm also not mentioning some of the math (eg ^=) because the few people who want it will find it, or some new functions that jaam's been able to add now that string and array vars are in the picture, simply because they're "just" new functions returning strings and arrays, whereas the purpose of the series I'm writing is to get a broad target audience of scripters up to speed with a shift in scripting practice, really.

 

I know when this all came to Oblivion modding - just as I was about to try my hand at a mod of my very own, way back when - it was just too much, and I just want to write the kind of guides I could've used back then, as someone without a coding background but able to do read and write Beth code because it's quite lingual.

 

So I'm thinking bitwise operations may just be better off with a separate tutorial of their own, rather than cram them in this one and muddy the water. Ie: I needed to talk about let and if eval anyway to tackle strings, arrays & UDFs later on; I mentioned "math and assign" in the same go because it's the kind of thing everybody has a use for, and the while loops are just plain better than most occasions of label/goto; not to mention I'll talk about foreach ones when I deal with strings and arrays anyway. To me, bitwise ops are a bit out of left field in the context of the series I'm writing, even though there's definitely a use for some explanation about them - also when it comes to manipulating weapon flags and the like. Thoughts? I just don't want to scare people off too much ;)

Share this post


Link to post

 

 

let bmHasKilledLegion = 1 << 0
let bmHasKilledNCR = 1 << 1
let bmHasKilledPG = 1 << 2

 

Is this correct syntax, btw? Shouldn't it be "let int := int << int" or something? Which'd correspond to "set int to Leftshift int int".

 

Oh hell if I know. Still haven't tried any of that yet, so you're probably right ;)

 

Well, even the old nvse docs mention a "LogicalXOr" which I suppose is similarly really a bitwise xor. And LogicalNot ("!"), which however does seem to function more as "logical" operator (like && and ||) when placed in front of a condition that returns a boolean, even if it is a bitwise one. What a naming mess, heh.

The LogicalNot sounds like a real logical/boolean Not. A bitwise not for any value but zero would still return true when evaluated as a boolean. If we look at the 8bit value 1, the bitwise negation/compliment (the "not") of that is 11111110b (254); a bitwise not simply flips all the bits. In a boolean context, 1 and 254 are the same thing -- both are 'true' since both are non-zero. Not is a 'funny' one though; it's the only bitwise operator that only has one operand. All the others take two.

 

The normal bitwise operator for not is the tilde ("~") while the exclamation point is usually the boolean one, and it sounds like that's the case here.

 

There is no such thing as a Logical/boolean XOR though, so that one is certainly bitwise. The boolean counterpart to xor is an expression like ((a or B) and (a != B)).

 

I gotta wonder though whether bitwise operations really qualify as new NVSE4 stuff, considering it's been in there since the fose days and the only new thing about it I can see is some of the symbols. I'm also not mentioning some of the math (eg ^=) because the few people who want it will find it, or some new functions that jaam's been able to add now that string and array vars are in the picture, simply because they're "just" new functions returning strings and arrays, whereas the purpose of the series I'm writing is to get a broad target audience of scripters up to speed with a shift in scripting practice, really.

I'd probably just agree with you and leave the bitwise stuff completely out in that case, except to make a note that some of the functions (like LogicalXOR) are actually bitwise, but some (like LogicalNOT) are not.

 

A good example (better than mine) of when the bitwise operators are handy -- or rather, would be handy -- is with GetPlayerControlsDisabled and the related Set/Get functions. Those could all be done with a single variable (rather than 5 of them) if the GECK natively supported bitwise operations.

 

edit: damn smileys.

Share this post


Link to post

It figures as soon as I click send that I would think of another few. Consider these just for your personal use/education, no need to put them in the tutorials.. ;)

 

You can tell if two formIDs come from the same mod if their mod index is the same, obviously. The formID format of XXYYYYYY is a perfect example for bitwise operations. The mod index of any form is "formid >> 24"; if "(thisform >> 24) == (thatform >> 24)" then both come from the same mod.

 

You can also build a refid without buildref, with something like "baseid | (gettmodindex('somemod.esp') << 24)" where baseid is the formID YYYYYY part. There is probably no good reason to do this right now, but if you were in a loop doing a lot of buildrefs for some reason, doing it the bitwise way would be a *lot* faster.

 

Likewise if you want to multiply or divide an integer by two (and cut off the remainder if dividing), shifting left one or right one is a lot faster than actually multiplying or dividing.

 

You can check if an integer is even or odd by just ANDing it by 1. "someVar & 1" is 1 if it's odd, 0 if it's even. Without bitwise operators the canonical way to do this is to divide the number by two, multiply the result by 2, and see if you get the original number back. It will be off by 1 if the number is odd. This is also FAR slower than a bitwise AND.

Share this post


Link to post

Yep, definitely useful and underused (storing and retrieving enabled player controls and all the stuff that comes with bit flags sure looks handy), but like I said, I'll probably skip it for this particular series because it doesn't quite fall within the scope of what I'm trying to do with this series.

 

Question though:

 

You can also build a refid without buildref, with something like "baseid | (gettmodindex('somemod.esp') << 24)" where baseid is the formID YYYYYY part. There is probably no good reason to do this right now, but if you were in a loop doing a lot of buildrefs for some reason, doing it the bitwise way would be a *lot* faster.

 

I just tried that and the compiler complains about not knowing what to do with that hex formid string. Converting it to decimal, the logicalor returns the decimal equivalent of the formID, which is good. Still, no way to really pass a decimal int to a ref var, I think, or a hex string for that matter: "let rForm := 3D000ADE" doesn't compile.

Share this post


Link to post

Well you can try putting it in a string, a lot of the OBSE things that take an integer/formid will also take a string, assuming it's a hex value.

 

Does

let rForm := "3D000ADE"
work?

Share this post


Link to post

A bitwise operation is what GetBuildRef do, and C++ is faster than script :)

 

@prideslayer: I don't think it works. I don't remember implicit conversion from string to integer.

Share this post


Link to post

Hmm that's weird. The normal GECK ref vars can take integers ok.

 

they take them on compile time, but they don't return anything in a printc "%i" readout, other than zeroes

 

Well you can try putting it in a string, a lot of the OBSE things that take an integer/formid will also take a string, assuming it's a hex value.

 

Does

let rForm := "3D000ADE"
work?

 

 

 nope

Share this post


Link to post

 

Hmm that's weird. The normal GECK ref vars can take integers ok.

 

they take them on compile time, but they don't return anything in a printc "%i" readout, other than zeroes

 

They work at runtime too, that's how the NX EVFo's work. NX doesn't "really" understand Forms or Refs, just integers.

 

You would use %x to print them though, not %i.

 

Maybe I'm just going crazy/tired though.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...