Script Compiler Override
Overview[edit | edit source]
If using Let, if eval, While, Print and some other new NVSE functions, it is permitted to pass array elements, string variables and (perhaps user defined) nested function calls directly as parameters. However, the vanilla script compiler will prohibit direct passage to vanilla and older NVSE functions, complaining that the paramater type is invalid (it isn't sure what type the array element or function return will be, since it could potentially be any). Typically, this forces you to use an intermediary variable. Alternatively, the script compiler override (CO) allows you to ignore these 'misgivings', and assuming that you know the types are safe, compile scripts that will work correctly.
To use the CO, you prefix a script block type with an underscore (eg: Begin _GameMode). See the following examples for further details, and also the warning at the end of this article.
Example #1[edit | edit source]
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 ; * or _Menumode, _ScriptEffectStart, _OnActivate, _Function, 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[edit | edit source]
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[edit | edit source]
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:
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 ;)
Example #4[edit | edit source]
Maybe you got tired of trying to remember all those damn magic numbers for type codes, equipment locations, etc? In your main quest:
let Locations := Ar_Map "Upper Body"::2, "Weapon"::5, "Hat"::10 ... let TypeCodes := Ar_Map "Armor"::24, "Weapon"::40, "Alchemy"::47 ...
In some script:
array_var loc Begin _GameMode let loc := MyMainQuest.Locations ; * aside: this is a reference not wasteful copy ;) let rItem := PlayerREF.GetEquippedObject loc["Upper Body"] End
A Warning About '1-Lining'[edit | edit source]
It is easy to get carried away with the compiler override, especially if you have experience of real programming languages, since it is now possible to do things that used to take 10 or more lines of scripting in a single line. For example, suppose you wanted to pick a random weapon from a form list and give it to the player in a random condition, without the compiler override:
int iCount int iChoice float fCondition ref rItem Begin GameMode ; or whatever let iCount := ListGetCount SomeFormList let iChoice := Rand 0, iCount let rItem := ListGetNthForm SomeFormList, iChoice let fCondition := Rand 0, 1 PlayerREF.AddItemHealthPercent rItem, 1, fCondition End
With the CO, you can do this as a 1-liner with no variable declaration:
Begin _GameMode PlayerREF.AddItemHealthPercent (ListGetNthForm SomeFormList, (Rand 0, (ListGetCount SomeFormList))), 1, (Rand 0, 1) End
'1-liners' full of nested function calls like this are logically correct and may work fine in game. Sometimes they won't, possibly instead giving a CTD. It may be prudent (and probably more readable) to replace excessive literal '1-liners' with a UDF call.
See Also[edit | edit source]
- Array Variable
- String Variable
- User Defined Function
- You don't need to use the CO with: Let, Eval, While calls.